diff --git a/.cargo/config.toml b/.cargo/config.toml
index 8f1ff8a7..33cf3dc1 100644
--- a/.cargo/config.toml
+++ b/.cargo/config.toml
@@ -2,8 +2,6 @@
 lint = "clippy --workspace --tests --examples --bins -- -Dclippy::todo"
 lint-all = "clippy --workspace --all-features --tests --examples --bins -- -Dclippy::todo"
 
-ci-doctest = "test --workspace --all-features --doc --no-fail-fast -- --nocapture"
-
 # just check the library (without dev deps)
 ci-check-min = "hack --workspace check --no-default-features"
 ci-check-lib = "hack --workspace --feature-powerset --depth=2 --exclude-features=io-uring check"
@@ -15,11 +13,9 @@ ci-check-linux = "hack --workspace --feature-powerset --depth=2 check --tests --
 
 # tests avoiding io-uring feature
 ci-test = "hack --feature-powerset --depth=2 --exclude-features=io-uring test --lib --tests --no-fail-fast -- --nocapture"
-ci-test-rustls-020 = "hack --feature-powerset --depth=2 --exclude-features=io-uring,rustls-0_21 test --lib --tests --no-fail-fast -- --nocapture"
-ci-test-rustls-021 = "hack --feature-powerset --depth=2 --exclude-features=io-uring,rustls-0_20 test --lib --tests --no-fail-fast -- --nocapture"
-
-# tests avoiding io-uring feature on Windows
-ci-test-win = "hack --feature-powerset --depth=2 --exclude-features=io-uring test --lib --tests --no-fail-fast -- --nocapture"
+ci-test-rustls-020 = "hack --feature-powerset --depth=2 --exclude-features=io-uring,rustls-0_21,rustls-0_22 test --lib --tests --no-fail-fast -- --nocapture"
+ci-test-rustls-021 = "hack --feature-powerset --depth=2 --exclude-features=io-uring,rustls-0_20,rustls-0_22 test --lib --tests --no-fail-fast -- --nocapture"
+ci-test-rustls-022 = "hack --feature-powerset --depth=2 --exclude-features=io-uring,rustls-0_20,rustls-0_21 test --lib --tests --no-fail-fast -- --nocapture"
 
 # test with io-uring feature
-ci-test-linux = "hack --feature-powerset --depth=2 --exclude-features=rustls-0_20 test --lib --tests --no-fail-fast -- --nocapture"
+ci-test-linux = "hack --feature-powerset --depth=2 --exclude-features=rustls-0_20,rustls-0_21 test --lib --tests --no-fail-fast -- --nocapture"
diff --git a/.github/workflows/ci-post-merge.yml b/.github/workflows/ci-post-merge.yml
index 357690bb..27de0429 100644
--- a/.github/workflows/ci-post-merge.yml
+++ b/.github/workflows/ci-post-merge.yml
@@ -22,7 +22,6 @@ jobs:
           - { name: macOS, os: macos-latest, triple: x86_64-apple-darwin }
           - { name: Windows, os: windows-latest, triple: x86_64-pc-windows-msvc }
           - { name: Windows (MinGW), os: windows-latest, triple: x86_64-pc-windows-gnu }
-          - { name: Windows (32-bit), os: windows-latest, triple: i686-pc-windows-msvc }
         version:
           - nightly
 
@@ -44,22 +43,22 @@ jobs:
 
       - name: Install OpenSSL
         if: matrix.target.os == 'windows-latest'
-        run: choco install openssl -y --forcex64 --no-progress
-      - name: Set OpenSSL dir in env
-        if: matrix.target.os == 'windows-latest'
+        shell: bash
         run: |
-          echo 'OPENSSL_DIR=C:\Program Files\OpenSSL-Win64' | Out-File -FilePath $env:GITHUB_ENV -Append
-          echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' | Out-File -FilePath $env:GITHUB_ENV -Append
+          set -e
+          choco install openssl --version=1.1.1.2100 -y --no-progress
+          echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' >> $GITHUB_ENV
+          echo "RUSTFLAGS=-C target-feature=+crt-static" >> $GITHUB_ENV
 
       - name: Install Rust (${{ matrix.version }})
         uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
         with:
           toolchain: ${{ matrix.version }}
 
-      - name: Install cargo-hack and cargo-ci-cache-clean
-        uses: taiki-e/install-action@v2.25.2
+      - name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
+        uses: taiki-e/install-action@v2.33.17
         with:
-          tool: cargo-hack,cargo-ci-cache-clean
+          tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean
 
       - name: check lib
         if: >
@@ -84,10 +83,8 @@ jobs:
         run: cargo ci-check-linux
 
       - name: tests
-        if: >
-          matrix.target.os != 'ubuntu-latest'
-          && matrix.target.triple != 'x86_64-pc-windows-gnu'
-        run: cargo ci-test
+        if: matrix.target.os != 'ubuntu-latest'
+        run: just test
       - name: tests
         if: matrix.target.os == 'ubuntu-latest'
         run: >-
@@ -115,7 +112,7 @@ jobs:
           toolchain: nightly
 
       - name: Install cargo-hack & cargo-minimal-versions
-        uses: taiki-e/install-action@v2.25.2
+        uses: taiki-e/install-action@v2.33.17
         with:
           tool: cargo-hack,cargo-minimal-versions
 
@@ -132,7 +129,7 @@ jobs:
         uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
 
       - name: Install cargo-nextest
-        uses: taiki-e/install-action@v2.25.2
+        uses: taiki-e/install-action@v2.33.17
         with:
           tool: cargo-nextest
 
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index a3b47270..32893ee7 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -23,9 +23,8 @@ jobs:
           - { name: macOS, os: macos-latest, triple: x86_64-apple-darwin }
           - { name: Windows, os: windows-latest, triple: x86_64-pc-windows-msvc }
           - { name: Windows (MinGW), os: windows-latest, triple: x86_64-pc-windows-gnu }
-          - { name: Windows (32-bit), os: windows-latest, triple: i686-pc-windows-msvc }
         version:
-          - { name: msrv, version: 1.65.0 }
+          - { name: msrv, version: 1.70.0 }
           - { name: stable, version: stable }
 
     name: ${{ matrix.target.name }} / ${{ matrix.version.name }}
@@ -46,33 +45,29 @@ jobs:
 
       - name: Install OpenSSL
         if: matrix.target.os == 'windows-latest'
-        run: choco install openssl -y --forcex64 --no-progress
-      - name: Set OpenSSL dir in env
-        if: matrix.target.os == 'windows-latest'
+        shell: bash
         run: |
-          echo 'OPENSSL_DIR=C:\Program Files\OpenSSL-Win64' | Out-File -FilePath $env:GITHUB_ENV -Append
-          echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' | Out-File -FilePath $env:GITHUB_ENV -Append
+          set -e
+          choco install openssl --version=1.1.1.2100 -y --no-progress
+          echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' >> $GITHUB_ENV
+          echo "RUSTFLAGS=-C target-feature=+crt-static" >> $GITHUB_ENV
 
       - name: Install Rust (${{ matrix.version.name }})
         uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
         with:
           toolchain: ${{ matrix.version.version }}
 
-      - name: Install cargo-hack and cargo-ci-cache-clean
-        uses: taiki-e/install-action@v2.25.2
+      - name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
+        uses: taiki-e/install-action@v2.33.17
         with:
-          tool: cargo-hack,cargo-ci-cache-clean
+          tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean
 
       - name: Generate Cargo.lock
         run: cargo generate-lockfile
 
       - name: workaround MSRV issues
         if: matrix.version.name == 'msrv'
-        run: |
-          cargo update -p=time --precise=0.3.16
-          cargo update -p=clap --precise=4.3.24
-          cargo update -p=clap_lex --precise=0.5.0
-          cargo update -p=anstyle --precise=1.0.2
+        run: just downgrade-for-msrv
 
       - name: check lib
         if: >
@@ -83,7 +78,7 @@ jobs:
         if: matrix.target.os == 'ubuntu-latest'
         run: cargo ci-check-lib-linux
       - name: check lib
-        if: matrix.target.triple == 'x86_64-pc-windows-gnu'
+        if: matrix.target.triple != 'x86_64-pc-windows-gnu'
         run: cargo ci-check-min
 
       - name: check full
@@ -97,30 +92,15 @@ jobs:
         run: cargo ci-check-linux
 
       - name: tests
-        if: matrix.target.os == 'macos-latest'
-        run: cargo ci-test
-      - name: tests
-        if: >
-          matrix.target.os == 'windows-latest'
-          && matrix.target.triple != 'x86_64-pc-windows-gnu'
-        run: cargo ci-test-win
-      - name: tests
-        if: matrix.target.os == 'ubuntu-latest'
-        run: >-
-          sudo bash -c "
-          ulimit -Sl 512
-          && ulimit -Hl 512
-          && PATH=$PATH:/usr/share/rust/.cargo/bin
-          && RUSTUP_TOOLCHAIN=${{ matrix.version.version }} cargo ci-test-rustls-020
-          && RUSTUP_TOOLCHAIN=${{ matrix.version.version }} cargo ci-test-rustls-021
-          && RUSTUP_TOOLCHAIN=${{ matrix.version.version }} cargo ci-test-linux
-          "
+        run: just test
+
+      # TODO: re-instate some io-uring tests for PRs
 
       - name: CI cache clean
         run: cargo-ci-cache-clean
 
-  rustdoc:
-    name: rustdoc
+  docs:
+    name: Documentation
     runs-on: ubuntu-latest
 
     steps:
@@ -131,6 +111,10 @@ jobs:
         with:
           toolchain: nightly
 
-      - name: doc tests io-uring
-        run: |
-          sudo bash -c "ulimit -Sl 512 && ulimit -Hl 512 && PATH=$PATH:/usr/share/rust/.cargo/bin && RUSTUP_TOOLCHAIN=nightly cargo ci-doctest"
+      - name: Install just
+        uses: taiki-e/install-action@v2.33.17
+        with:
+          tool: just
+
+      - name: doc tests
+        run: just test-docs
diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml
index 28516225..a9074a51 100644
--- a/.github/workflows/coverage.yml
+++ b/.github/workflows/coverage.yml
@@ -23,7 +23,7 @@ jobs:
           components: llvm-tools-preview
 
       - name: Install cargo-llvm-cov
-        uses: taiki-e/install-action@v2.25.2
+        uses: taiki-e/install-action@v2.33.17
         with:
           tool: cargo-llvm-cov
 
@@ -31,7 +31,9 @@ jobs:
         run: cargo llvm-cov --workspace --all-features --codecov --output-path codecov.json
 
       - name: Upload coverage to Codecov
-        uses: codecov/codecov-action@v3.1.4
+        uses: codecov/codecov-action@v4.3.1
         with:
           files: codecov.json
           fail_ci_if_error: true
+        env:
+          CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index e0495b98..b6f88db5 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -55,12 +55,12 @@ jobs:
           toolchain: nightly-2023-10-10
 
       - name: Install just
-        uses: taiki-e/install-action@v2.25.2
+        uses: taiki-e/install-action@v2.33.17
         with:
           tool: just
 
       - name: Install cargo-check-external-types
-        uses: taiki-e/cache-cargo-install-action@v1.3.0
+        uses: taiki-e/cache-cargo-install-action@v2.0.0
         with:
           tool: cargo-check-external-types@0.1.10
 
diff --git a/.github/workflows/upload-doc.yml b/.github/workflows/upload-doc.yml
index 62be1753..2d70e7ff 100644
--- a/.github/workflows/upload-doc.yml
+++ b/.github/workflows/upload-doc.yml
@@ -30,7 +30,7 @@ jobs:
         run: echo '<meta http-equiv="refresh" content="0;url=actix_server/index.html">' > target/doc/index.html
 
       - name: Deploy to GitHub Pages
-        uses: JamesIves/github-pages-deploy-action@v4.5.0
+        uses: JamesIves/github-pages-deploy-action@v4.6.0
         with:
           folder: target/doc
           single-commit: true
diff --git a/Cargo.toml b/Cargo.toml
index 696de4a5..c130eaf9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,4 +1,5 @@
 [workspace]
+resolver = "2"
 members = [
   "actix-codec",
   "actix-macros",
@@ -12,12 +13,11 @@ members = [
   "local-channel",
   "local-waker",
 ]
-resolver = "2"
 
 [workspace.package]
 license = "MIT OR Apache-2.0"
 edition = "2021"
-rust-version = "1.65"
+rust-version = "1.70"
 
 [patch.crates-io]
 actix-codec = { path = "actix-codec" }
diff --git a/actix-codec/CHANGES.md b/actix-codec/CHANGES.md
index ec54e407..fa2bfddd 100644
--- a/actix-codec/CHANGES.md
+++ b/actix-codec/CHANGES.md
@@ -2,20 +2,20 @@
 
 ## Unreleased
 
+- Minimum supported Rust version (MSRV) is now 1.70.
+
+## 0.5.2
+
 - Minimum supported Rust version (MSRV) is now 1.65.
 
 ## 0.5.1
 
-- Logs emitted now use the `tracing` crate with `log` compatibility. [#451]
+- Logs emitted now use the `tracing` crate with `log` compatibility.
 - Minimum supported Rust version (MSRV) is now 1.49.
 
-[#451]: https://github.com/actix/actix-net/pull/451
-
 ## 0.5.0
 
-- Updated `tokio-util` dependency to `0.7.0`. [#446]
-
-[#446]: https://github.com/actix/actix-net/pull/446
+- Updated `tokio-util` dependency to `0.7.0`.
 
 ## 0.4.2
 
@@ -23,11 +23,8 @@
 
 ## 0.4.1
 
-- Added `LinesCodec.` [#338]
-- `Framed::poll_ready` flushes when the buffer is full. [#409]
-
-[#338]: https://github.com/actix/actix-net/pull/338
-[#409]: https://github.com/actix/actix-net/pull/409
+- Added `LinesCodec`.
+- `Framed::poll_ready` flushes when the buffer is full.
 
 ## 0.4.0
 
@@ -35,12 +32,10 @@
 
 ## 0.4.0-beta.1
 
-- Replace `pin-project` with `pin-project-lite`. [#237]
-- Upgrade `tokio` dependency to `1`. [#237]
-- Upgrade `tokio-util` dependency to `0.6`. [#237]
-- Upgrade `bytes` dependency to `1`. [#237]
-
-[#237]: https://github.com/actix/actix-net/pull/237
+- Replace `pin-project` with `pin-project-lite`.
+- Upgrade `tokio` dependency to `1`.
+- Upgrade `tokio-util` dependency to `0.6`.
+- Upgrade `bytes` dependency to `1`.
 
 ## 0.3.0
 
diff --git a/actix-codec/Cargo.toml b/actix-codec/Cargo.toml
index 91fefc7c..d71c7c25 100644
--- a/actix-codec/Cargo.toml
+++ b/actix-codec/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "actix-codec"
-version = "0.5.1"
+version = "0.5.2"
 authors = [
     "Nikolay Kim <fafhrd91@gmail.com>",
     "Rob Ede <robjtede@icloud.com>",
diff --git a/actix-macros/CHANGES.md b/actix-macros/CHANGES.md
index 570c4e1e..99ecfa97 100644
--- a/actix-macros/CHANGES.md
+++ b/actix-macros/CHANGES.md
@@ -2,6 +2,8 @@
 
 ## Unreleased
 
+- Minimum supported Rust version (MSRV) is now 1.70.
+
 ## 0.2.4
 
 - Update `syn` dependency to `2`.
diff --git a/actix-macros/Cargo.toml b/actix-macros/Cargo.toml
index b96daeb9..2f112ed4 100644
--- a/actix-macros/Cargo.toml
+++ b/actix-macros/Cargo.toml
@@ -31,7 +31,6 @@ proc-macro2 = "1.0.60"
 
 [dev-dependencies]
 actix-rt = "2"
-
 futures-util = { version = "0.3.17", default-features = false }
-rustversion = "1"
+rustversion-msrv = "0.99"
 trybuild = "1"
diff --git a/actix-macros/tests/trybuild.rs b/actix-macros/tests/trybuild.rs
index b37b84ba..5cfb1b6e 100644
--- a/actix-macros/tests/trybuild.rs
+++ b/actix-macros/tests/trybuild.rs
@@ -1,4 +1,4 @@
-#[rustversion::stable(1.65)] // MSRV
+#[rustversion_msrv::msrv]
 #[test]
 fn compile_macros() {
     let t = trybuild::TestCases::new();
diff --git a/actix-rt/CHANGES.md b/actix-rt/CHANGES.md
index dc2d0fc3..1535612b 100644
--- a/actix-rt/CHANGES.md
+++ b/actix-rt/CHANGES.md
@@ -2,6 +2,8 @@
 
 ## Unreleased
 
+- Minimum supported Rust version (MSRV) is now 1.70.
+
 ## 2.9.0
 
 - Add `actix_rt::System::runtime()` method to retrieve the underlying `actix_rt::Runtime` runtime.
@@ -10,150 +12,105 @@
 
 ## 2.8.0
 
-- Add `#[track_caller]` attribute to `spawn` functions and methods. [#454]
-- Update `tokio-uring` dependency to `0.4`. [#473]
+- Add `#[track_caller]` attribute to `spawn` functions and methods.
+- Update `tokio-uring` dependency to `0.4`.
 - Minimum supported Rust version (MSRV) is now 1.59.
 
-[#454]: https://github.com/actix/actix-net/pull/454
-[#473]: https://github.com/actix/actix-net/pull/473
-
 ## 2.7.0
 
-- Update `tokio-uring` dependency to `0.3`. [#448]
+- Update `tokio-uring` dependency to `0.3`.
 - Minimum supported Rust version (MSRV) is now 1.49.
 
-[#448]: https://github.com/actix/actix-net/pull/448
-
 ## 2.6.0
 
-- Update `tokio-uring` dependency to `0.2`. [#436]
-
-[#436]: https://github.com/actix/actix-net/pull/436
+- Update `tokio-uring` dependency to `0.2`.
 
 ## 2.5.1
 
-- Expose `System::with_tokio_rt` and `Arbiter::with_tokio_rt`. [#430]
-
-[#430]: https://github.com/actix/actix-net/pull/430
+- Expose `System::with_tokio_rt` and `Arbiter::with_tokio_rt`.
 
 ## 2.5.0
 
-- Add `System::run_with_code` to allow retrieving the exit code on stop. [#411]
-
-[#411]: https://github.com/actix/actix-net/pull/411
+- Add `System::run_with_code` to allow retrieving the exit code on stop.
 
 ## 2.4.0
 
-- Add `Arbiter::try_current` for situations where thread may or may not have Arbiter context. [#408]
-- Start io-uring with `System::new` when feature is enabled. [#395]
-
-[#395]: https://github.com/actix/actix-net/pull/395
-[#408]: https://github.com/actix/actix-net/pull/408
+- Add `Arbiter::try_current` for situations where thread may or may not have Arbiter context.
+- Start io-uring with `System::new` when feature is enabled.
 
 ## 2.3.0
 
-- The `spawn` method can now resolve with non-unit outputs. [#369]
-- Add experimental (semver-exempt) `io-uring` feature for enabling async file I/O on linux. [#374]
-
-[#369]: https://github.com/actix/actix-net/pull/369
-[#374]: https://github.com/actix/actix-net/pull/374
+- The `spawn` method can now resolve with non-unit outputs.
+- Add experimental (semver-exempt) `io-uring` feature for enabling async file I/O on linux.
 
 ## 2.2.0
 
-- **BREAKING** `ActixStream::{poll_read_ready, poll_write_ready}` methods now return `Ready` object in ok variant. [#293]
+- **BREAKING** `ActixStream::{poll_read_ready, poll_write_ready}` methods now return `Ready` object in ok variant.
   - Breakage is acceptable since `ActixStream` was not intended to be public.
 
-[#293]: https://github.com/actix/actix-net/pull/293
-
 ## 2.1.0
 
-- Add `ActixStream` extension trait to include readiness methods. [#276]
-- Re-export `tokio::net::TcpSocket` in `net` module [#282]
-
-[#276]: https://github.com/actix/actix-net/pull/276
-[#282]: https://github.com/actix/actix-net/pull/282
+- Add `ActixStream` extension trait to include readiness methods.
+- Re-export `tokio::net::TcpSocket` in `net` module
 
 ## 2.0.2
 
-- Add `Arbiter::handle` to get a handle of an owned Arbiter. [#274]
-- Add `System::try_current` for situations where actix may or may not be running a System. [#275]
-
-[#274]: https://github.com/actix/actix-net/pull/274
-[#275]: https://github.com/actix/actix-net/pull/275
+- Add `Arbiter::handle` to get a handle of an owned Arbiter.
+- Add `System::try_current` for situations where actix may or may not be running a System.
 
 ## 2.0.1
 
-- Expose `JoinError` from Tokio. [#271]
-
-[#271]: https://github.com/actix/actix-net/pull/271
+- Expose `JoinError` from Tokio.
 
 ## 2.0.0
 
-- Remove all Arbiter-local storage methods. [#262]
-- Re-export `tokio::pin`. [#262]
-
-[#262]: https://github.com/actix/actix-net/pull/262
+- Remove all Arbiter-local storage methods.
+- Re-export `tokio::pin`.
 
 ## 2.0.0-beta.3
 
-- Remove `run_in_tokio`, `attach_to_tokio` and `AsyncSystemRunner`. [#253]
-- Return `JoinHandle` from `actix_rt::spawn`. [#253]
-- Remove old `Arbiter::spawn`. Implementation is now inlined into `actix_rt::spawn`. [#253]
-- Rename `Arbiter::{send => spawn}` and `Arbiter::{exec_fn => spawn_fn}`. [#253]
-- Remove `Arbiter::exec`. [#253]
-- Remove deprecated `Arbiter::local_join` and `Arbiter::is_running`. [#253]
-- `Arbiter::spawn` now accepts !Unpin futures. [#256]
-- `System::new` no longer takes arguments. [#257]
-- Remove `System::with_current`. [#257]
-- Remove `Builder`. [#257]
-- Add `System::with_init` as replacement for `Builder::run`. [#257]
-- Rename `System::{is_set => is_registered}`. [#257]
-- Add `ArbiterHandle` for sending messages to non-current-thread arbiters. [#257].
-- `System::arbiter` now returns an `&ArbiterHandle`. [#257]
-- `Arbiter::current` now returns an `ArbiterHandle` instead. [#257]
-- `Arbiter::join` now takes self by value. [#257]
-
-[#253]: https://github.com/actix/actix-net/pull/253
-[#254]: https://github.com/actix/actix-net/pull/254
-[#256]: https://github.com/actix/actix-net/pull/256
-[#257]: https://github.com/actix/actix-net/pull/257
+- Remove `run_in_tokio`, `attach_to_tokio` and `AsyncSystemRunner`.
+- Return `JoinHandle` from `actix_rt::spawn`.
+- Remove old `Arbiter::spawn`. Implementation is now inlined into `actix_rt::spawn`.
+- Rename `Arbiter::{send => spawn}` and `Arbiter::{exec_fn => spawn_fn}`.
+- Remove `Arbiter::exec`.
+- Remove deprecated `Arbiter::local_join` and `Arbiter::is_running`.
+- `Arbiter::spawn` now accepts !Unpin futures.
+- `System::new` no longer takes arguments.
+- Remove `System::with_current`.
+- Remove `Builder`.
+- Add `System::with_init` as replacement for `Builder::run`.
+- Rename `System::{is_set => is_registered}`.
+- Add `ArbiterHandle` for sending messages to non-current-thread arbiters.
+- `System::arbiter` now returns an `&ArbiterHandle`.
+- `Arbiter::current` now returns an `ArbiterHandle` instead.
+- `Arbiter::join` now takes self by value.
 
 ## 2.0.0-beta.2
 
-- Add `task` mod with re-export of `tokio::task::{spawn_blocking, yield_now, JoinHandle}` [#245]
+- Add `task` mod with re-export of `tokio::task::{spawn_blocking, yield_now, JoinHandle}`
 - Add default "macros" feature to allow faster compile times when using `default-features=false`.
 
-[#245]: https://github.com/actix/actix-net/pull/245
-
 ## 2.0.0-beta.1
 
-- Add `System::attach_to_tokio` method. [#173]
-- Update `tokio` dependency to `1.0`. [#236]
-- Rename `time` module `delay_for` to `sleep`, `delay_until` to `sleep_until`, `Delay` to `Sleep` to stay aligned with Tokio's naming. [#236]
+- Add `System::attach_to_tokio` method.
+- Update `tokio` dependency to `1.0`.
+- Rename `time` module `delay_for` to `sleep`, `delay_until` to `sleep_until`, `Delay` to `Sleep` to stay aligned with Tokio's naming.
 - Remove `'static` lifetime requirement for `Runtime::block_on` and `SystemRunner::block_on`.
-  - These methods now accept `&self` when calling. [#236]
-- Remove `'static` lifetime requirement for `System::run` and `Builder::run`. [#236]
-- `Arbiter::spawn` now panics when `System` is not in scope. [#207]
-- Fix work load issue by removing `PENDING` thread local. [#207]
-
-[#207]: https://github.com/actix/actix-net/pull/207
-[#236]: https://github.com/actix/actix-net/pull/236
+  - These methods now accept `&self` when calling.
+- Remove `'static` lifetime requirement for `System::run` and `Builder::run`.
+- `Arbiter::spawn` now panics when `System` is not in scope.
+- Fix work load issue by removing `PENDING` thread local.
 
 ## 1.1.1
 
-- Fix memory leak due to [#94] (see [#129] for more detail)
-
-[#129]: https://github.com/actix/actix-net/issues/129
+- Fix memory leak due to
 
 ## 1.1.0 _(YANKED)_
 
-- Expose `System::is_set` to check if current system has ben started [#99]
-- Add `Arbiter::is_running` to check if event loop is running [#124]
-- Add `Arbiter::local_join` associated function to get be able to `await` for spawned futures [#94]
-
-[#94]: https://github.com/actix/actix-net/pull/94
-[#99]: https://github.com/actix/actix-net/pull/99
-[#124]: https://github.com/actix/actix-net/pull/124
+- Expose `System::is_set` to check if current system has ben started
+- Add `Arbiter::is_running` to check if event loop is running
+- Add `Arbiter::local_join` associated function to get be able to `await` for spawned futures
 
 ## 1.0.0
 
diff --git a/actix-rt/src/arbiter.rs b/actix-rt/src/arbiter.rs
index 48cc752f..ee647aef 100644
--- a/actix-rt/src/arbiter.rs
+++ b/actix-rt/src/arbiter.rs
@@ -16,7 +16,7 @@ use crate::system::{System, SystemCommand};
 pub(crate) static COUNT: AtomicUsize = AtomicUsize::new(0);
 
 thread_local!(
-    static HANDLE: RefCell<Option<ArbiterHandle>> = RefCell::new(None);
+    static HANDLE: RefCell<Option<ArbiterHandle>> = const { RefCell::new(None) };
 );
 
 pub(crate) enum ArbiterCommand {
diff --git a/actix-rt/src/lib.rs b/actix-rt/src/lib.rs
index 6f8057de..fc2a56ba 100644
--- a/actix-rt/src/lib.rs
+++ b/actix-rt/src/lib.rs
@@ -34,6 +34,7 @@
 //! ```
 //!
 //! # `io-uring` Support
+//!
 //! There is experimental support for using io-uring with this crate by enabling the
 //! `io-uring` feature. For now, it is semver exempt.
 //!
diff --git a/actix-rt/src/system.rs b/actix-rt/src/system.rs
index 4c276beb..367a2d21 100644
--- a/actix-rt/src/system.rs
+++ b/actix-rt/src/system.rs
@@ -16,7 +16,7 @@ use crate::{arbiter::ArbiterHandle, Arbiter};
 static SYSTEM_COUNT: AtomicUsize = AtomicUsize::new(0);
 
 thread_local!(
-    static CURRENT: RefCell<Option<System>> = RefCell::new(None);
+    static CURRENT: RefCell<Option<System>> = const { RefCell::new(None) };
 );
 
 /// A manager for a per-thread distributed async runtime.
diff --git a/actix-server/CHANGES.md b/actix-server/CHANGES.md
index 9150031e..ce4c6b27 100644
--- a/actix-server/CHANGES.md
+++ b/actix-server/CHANGES.md
@@ -2,6 +2,8 @@
 
 ## Unreleased
 
+- Minimum supported Rust version (MSRV) is now 1.70.
+
 ## 2.3.0
 
 - Add support for MultiPath TCP (MPTCP) with `MpTcp` enum and `ServerBuilder::mptcp()` method.
@@ -10,9 +12,7 @@
 ## 2.2.0
 
 - Minimum supported Rust version (MSRV) is now 1.59.
-- Update `tokio-uring` dependency to `0.4`. [#473]
-
-[#473]: https://github.com/actix/actix-net/pull/473
+- Update `tokio-uring` dependency to `0.4`.
 
 ## 2.1.1
 
@@ -20,12 +20,9 @@
 
 ## 2.1.0
 
-- Update `tokio-uring` dependency to `0.3`. [#448]
-- Logs emitted now use the `tracing` crate with `log` compatibility. [#448]
-- Wait for accept thread to stop before sending completion signal. [#443]
-
-[#443]: https://github.com/actix/actix-net/pull/443
-[#448]: https://github.com/actix/actix-net/pull/448
+- Update `tokio-uring` dependency to `0.3`.
+- Logs emitted now use the `tracing` crate with `log` compatibility.
+- Wait for accept thread to stop before sending completion signal.
 
 ## 2.0.0
 
@@ -33,9 +30,7 @@
 
 ## 2.0.0-rc.4
 
-- Update `tokio-uring` dependency to `0.2`. [#436]
-
-[#436]: https://github.com/actix/actix-net/pull/436
+- Update `tokio-uring` dependency to `0.2`.
 
 ## 2.0.0-rc.3
 
@@ -43,117 +38,80 @@
 
 ## 2.0.0-rc.2
 
-- Simplify `TestServer`. [#431]
-
-[#431]: https://github.com/actix/actix-net/pull/431
+- Simplify `TestServer`.
 
 ## 2.0.0-rc.1
 
-- Hide implementation details of `Server`. [#424]
-- `Server` now runs only after awaiting it. [#425]
-
-[#424]: https://github.com/actix/actix-net/pull/424
-[#425]: https://github.com/actix/actix-net/pull/425
+- Hide implementation details of `Server`.
+- `Server` now runs only after awaiting it.
 
 ## 2.0.0-beta.9
 
-- Restore `Arbiter` support lost in `beta.8`. [#417]
-
-[#417]: https://github.com/actix/actix-net/pull/417
+- Restore `Arbiter` support lost in `beta.8`.
 
 ## 2.0.0-beta.8
 
-- Fix non-unix signal handler. [#410]
-
-[#410]: https://github.com/actix/actix-net/pull/410
+- Fix non-unix signal handler.
 
 ## 2.0.0-beta.7
 
-- Server can be started in regular Tokio runtime. [#408]
-- Expose new `Server` type whose `Future` impl resolves when server stops. [#408]
-- Rename `Server` to `ServerHandle`. [#407]
-- Add `Server::handle` to obtain handle to server. [#408]
-- Rename `ServerBuilder::{maxconn => max_concurrent_connections}`. [#407]
-- Deprecate crate-level `new` shortcut for server builder. [#408]
+- Server can be started in regular Tokio runtime.
+- Expose new `Server` type whose `Future` impl resolves when server stops.
+- Rename `Server` to `ServerHandle`.
+- Add `Server::handle` to obtain handle to server.
+- Rename `ServerBuilder::{maxconn => max_concurrent_connections}`.
+- Deprecate crate-level `new` shortcut for server builder.
 - Minimum supported Rust version (MSRV) is now 1.52.
 
-[#407]: https://github.com/actix/actix-net/pull/407
-[#408]: https://github.com/actix/actix-net/pull/408
-
 ## 2.0.0-beta.6
 
-- Add experimental (semver-exempt) `io-uring` feature for enabling async file I/O on linux. [#374]
-- Server no long listens to `SIGHUP` signal. Previously, the received was not used but did block subsequent exit signals from working. [#389]
-- Remove `config` module. `ServiceConfig`, `ServiceRuntime` public types are removed due to this change. [#349]
-- Remove `ServerBuilder::configure` [#349]
-
-[#374]: https://github.com/actix/actix-net/pull/374
-[#349]: https://github.com/actix/actix-net/pull/349
-[#389]: https://github.com/actix/actix-net/pull/389
+- Add experimental (semver-exempt) `io-uring` feature for enabling async file I/O on linux.
+- Server no long listens to `SIGHUP` signal. Previously, the received was not used but did block subsequent exit signals from working.
+- Remove `config` module. `ServiceConfig`, `ServiceRuntime` public types are removed due to this change.
+- Remove `ServerBuilder::configure`.
 
 ## 2.0.0-beta.5
 
-- Server shutdown notifies all workers to exit regardless if shutdown is graceful. This causes all workers to shutdown immediately in force shutdown case. [#333]
-
-[#333]: https://github.com/actix/actix-net/pull/333
+- Server shutdown notifies all workers to exit regardless if shutdown is graceful. This causes all workers to shutdown immediately in force shutdown case.
 
 ## 2.0.0-beta.4
 
 - Prevent panic when `shutdown_timeout` is very large. [f9262db]
 
-[f9262db]: https://github.com/actix/actix-net/commit/f9262db
-
 ## 2.0.0-beta.3
 
-- Hidden `ServerBuilder::start` method has been removed. Use `ServerBuilder::run`. [#246]
-- Add retry for EINTR signal (`io::Interrupted`) in `Accept`'s poll loop. [#264]
-- Add `ServerBuilder::worker_max_blocking_threads` to customize blocking thread pool size. [#265]
-- Update `actix-rt` to `2.0.0`. [#273]
-
-[#246]: https://github.com/actix/actix-net/pull/246
-[#264]: https://github.com/actix/actix-net/pull/264
-[#265]: https://github.com/actix/actix-net/pull/265
-[#273]: https://github.com/actix/actix-net/pull/273
+- Hidden `ServerBuilder::start` method has been removed. Use `ServerBuilder::run`.
+- Add retry for EINTR signal (`io::Interrupted`) in `Accept`'s poll loop.
+- Add `ServerBuilder::worker_max_blocking_threads` to customize blocking thread pool size.
+- Update `actix-rt` to `2.0.0`.
 
 ## 2.0.0-beta.2
 
-- Merge `actix-testing` to `actix-server` as `test_server` mod. [#242]
-
-[#242]: https://github.com/actix/actix-net/pull/242
+- Merge `actix-testing` to `actix-server` as `test_server` mod.
 
 ## 2.0.0-beta.1
 
-- Added explicit info log message on accept queue pause. [#215]
-- Prevent double registration of sockets when back-pressure is resolved. [#223]
-- Update `mio` dependency to `0.7.3`. [#239]
-- Remove `socket2` dependency. [#239]
-- `ServerBuilder::backlog` now accepts `u32` instead of `i32`. [#239]
-- Remove `AcceptNotify` type and pass `WakerQueue` to `Worker` to wake up `Accept`'s `Poll`. [#239]
-- Convert `mio::net::TcpStream` to `actix_rt::net::TcpStream`(`UnixStream` for uds) using `FromRawFd` and `IntoRawFd`(`FromRawSocket` and `IntoRawSocket` on windows). [#239]
-- Remove `AsyncRead` and `AsyncWrite` trait bound for `socket::FromStream` trait. [#239]
-
-[#215]: https://github.com/actix/actix-net/pull/215
-[#223]: https://github.com/actix/actix-net/pull/223
-[#239]: https://github.com/actix/actix-net/pull/239
+- Added explicit info log message on accept queue pause.
+- Prevent double registration of sockets when back-pressure is resolved.
+- Update `mio` dependency to `0.7.3`.
+- Remove `socket2` dependency.
+- `ServerBuilder::backlog` now accepts `u32` instead of `i32`.
+- Remove `AcceptNotify` type and pass `WakerQueue` to `Worker` to wake up `Accept`'s `Poll`.
+- Convert `mio::net::TcpStream` to `actix_rt::net::TcpStream`(`UnixStream` for uds) using `FromRawFd` and `IntoRawFd`(`FromRawSocket` and `IntoRawSocket` on windows).
+- Remove `AsyncRead` and `AsyncWrite` trait bound for `socket::FromStream` trait.
 
 ## 1.0.4
 
 - Update actix-codec to 0.3.0.
-- Workers must be greater than 0. [#167]
-
-[#167]: https://github.com/actix/actix-net/pull/167
+- Workers must be greater than 0.
 
 ## 1.0.3
 
-- Replace deprecated `net2` crate with `socket2` [#140]
-
-[#140]: https://github.com/actix/actix-net/pull/140
+- Replace deprecated `net2` crate with `socket2`.
 
 ## 1.0.2
 
-- Avoid error by calling `reregister()` on Windows [#103]
-
-[#103]: https://github.com/actix/actix-net/pull/103
+- Avoid error by calling `reregister()` on Windows.
 
 ## 1.0.1
 
diff --git a/actix-server/Cargo.toml b/actix-server/Cargo.toml
index 7c65ec81..1139a6c0 100644
--- a/actix-server/Cargo.toml
+++ b/actix-server/Cargo.toml
@@ -10,7 +10,7 @@ description = "General purpose TCP server built for the Actix ecosystem"
 keywords = ["network", "tcp", "server", "framework", "async"]
 categories = ["network-programming", "asynchronous"]
 homepage = "https://actix.rs"
-repository = "https://github.com/actix/actix-net.git"
+repository = "https://github.com/actix/actix-net/tree/master/actix-server"
 license = "MIT OR Apache-2.0"
 edition.workspace = true
 rust-version.workspace = true
diff --git a/actix-service/CHANGES.md b/actix-service/CHANGES.md
index 9a3b70a3..0ef39dc1 100644
--- a/actix-service/CHANGES.md
+++ b/actix-service/CHANGES.md
@@ -2,7 +2,7 @@
 
 ## Unreleased
 
-- Minimum supported Rust version (MSRV) is now 1.65.
+- Minimum supported Rust version (MSRV) is now 1.70.
 
 ## 2.0.2
 
diff --git a/actix-service/src/apply.rs b/actix-service/src/apply.rs
index d33bdcd8..8c7097c0 100644
--- a/actix-service/src/apply.rs
+++ b/actix-service/src/apply.rs
@@ -208,15 +208,13 @@ where
 
 #[cfg(test)]
 mod tests {
-    use core::task::Poll;
-
     use futures_util::future::lazy;
 
     use super::*;
     use crate::{
         ok,
         pipeline::{pipeline, pipeline_factory},
-        Ready, Service, ServiceFactory,
+        Ready,
     };
 
     #[derive(Clone)]
diff --git a/actix-service/src/fn_service.rs b/actix-service/src/fn_service.rs
index 2dac8697..c0f8a9c3 100644
--- a/actix-service/src/fn_service.rs
+++ b/actix-service/src/fn_service.rs
@@ -351,7 +351,6 @@ mod tests {
     use futures_util::future::lazy;
 
     use super::*;
-    use crate::{ok, Service, ServiceFactory};
 
     #[actix_rt::test]
     async fn test_fn_service() {
diff --git a/actix-service/src/map.rs b/actix-service/src/map.rs
index 49ddd9fe..97aec16e 100644
--- a/actix-service/src/map.rs
+++ b/actix-service/src/map.rs
@@ -202,9 +202,7 @@ mod tests {
     use futures_util::future::lazy;
 
     use super::*;
-    use crate::{
-        ok, IntoServiceFactory, Ready, Service, ServiceExt, ServiceFactory, ServiceFactoryExt,
-    };
+    use crate::{ok, IntoServiceFactory, Ready, ServiceExt, ServiceFactoryExt};
 
     struct Srv;
 
diff --git a/actix-service/src/map_err.rs b/actix-service/src/map_err.rs
index 780a64c7..da77b242 100644
--- a/actix-service/src/map_err.rs
+++ b/actix-service/src/map_err.rs
@@ -205,9 +205,7 @@ mod tests {
     use futures_util::future::lazy;
 
     use super::*;
-    use crate::{
-        err, ok, IntoServiceFactory, Ready, Service, ServiceExt, ServiceFactory, ServiceFactoryExt,
-    };
+    use crate::{err, ok, IntoServiceFactory, Ready, ServiceExt, ServiceFactoryExt};
 
     struct Srv;
 
diff --git a/actix-service/src/transform.rs b/actix-service/src/transform.rs
index e39f3058..e29d2b2b 100644
--- a/actix-service/src/transform.rs
+++ b/actix-service/src/transform.rs
@@ -226,7 +226,6 @@ mod tests {
     use actix_utils::future::{ready, Ready};
 
     use super::*;
-    use crate::Service;
 
     // pseudo-doctest for Transform trait
     pub struct TimeoutTransform {
diff --git a/actix-tls/CHANGES.md b/actix-tls/CHANGES.md
index d6f16209..3a5dafae 100644
--- a/actix-tls/CHANGES.md
+++ b/actix-tls/CHANGES.md
@@ -2,6 +2,12 @@
 
 ## Unreleased
 
+- Minimum supported Rust version (MSRV) is now 1.70.
+
+## 3.3.0
+
+- Add `rustls-0_22` create feature which excludes any root certificate methods or re-exports.
+
 ## 3.2.0
 
 - Support Rustls v0.22.
diff --git a/actix-tls/Cargo.toml b/actix-tls/Cargo.toml
index 7134b288..63805efd 100644
--- a/actix-tls/Cargo.toml
+++ b/actix-tls/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "actix-tls"
-version = "3.2.0"
+version = "3.3.0"
 authors = [
     "Nikolay Kim <fafhrd91@gmail.com>",
     "Rob Ede <robjtede@icloud.com>",
@@ -57,8 +57,9 @@ rustls-0_21-webpki-roots = ["tokio-rustls-024", "webpki-roots-025"]
 rustls-0_21-native-roots = ["tokio-rustls-024", "dep:rustls-native-certs-06"]
 
 # use rustls v0.22 impls
-rustls-0_22-webpki-roots = ["dep:tokio-rustls-025", "dep:rustls-pki-types-1", "dep:webpki-roots-026"]
-rustls-0_22-native-roots = ["dep:tokio-rustls-025", "dep:rustls-pki-types-1", "dep:rustls-native-certs-07"]
+rustls-0_22 = ["dep:tokio-rustls-025", "dep:rustls-pki-types-1"]
+rustls-0_22-webpki-roots = ["rustls-0_22", "dep:webpki-roots-026"]
+rustls-0_22-native-roots = ["rustls-0_22", "dep:rustls-native-certs-07"]
 
 # use native-tls impls
 native-tls = ["dep:tokio-native-tls"]
@@ -116,11 +117,11 @@ bytes = "1"
 env_logger = "0.11"
 futures-util = { version = "0.3.17", default-features = false, features = ["sink"] }
 itertools = "0.12"
-rcgen = "0.11"
+rcgen = "0.12"
 rustls-pemfile = "2"
 tokio-rustls-025 = { package = "tokio-rustls", version = "0.25" }
 trust-dns-resolver = "0.23"
 
 [[example]]
 name = "accept-rustls"
-required-features = ["accept", "rustls-0_22-webpki-roots"]
+required-features = ["accept", "rustls-0_22"]
diff --git a/actix-tls/README.md b/actix-tls/README.md
new file mode 100644
index 00000000..555a601b
--- /dev/null
+++ b/actix-tls/README.md
@@ -0,0 +1,21 @@
+# `actix-tls`
+
+> TLS acceptor and connector services for the Actix ecosystem.
+
+<!-- prettier-ignore-start -->
+
+[![crates.io](https://img.shields.io/crates/v/actix-tls?label=latest)](https://crates.io/crates/actix-tls)
+[![Documentation](https://docs.rs/actix-tls/badge.svg?version=3.3.0)](https://docs.rs/actix-tls/3.3.0)
+[![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
+![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-tls.svg)
+<br />
+[![Dependency Status](https://deps.rs/crate/actix-tls/3.3.0/status.svg)](https://deps.rs/crate/actix-tls/3.3.0)
+![Download](https://img.shields.io/crates/d/actix-tls.svg)
+[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
+
+<!-- prettier-ignore-end -->
+
+## Resources
+
+- [Library Documentation](https://docs.rs/actix-tls)
+- [Examples](/actix-tls/examples)
diff --git a/actix-tls/src/accept/mod.rs b/actix-tls/src/accept/mod.rs
index e884bd33..2302723e 100644
--- a/actix-tls/src/accept/mod.rs
+++ b/actix-tls/src/accept/mod.rs
@@ -22,10 +22,7 @@ pub use rustls_0_20 as rustls;
 #[cfg(feature = "rustls-0_21")]
 pub mod rustls_0_21;
 
-#[cfg(any(
-    feature = "rustls-0_22-webpki-roots",
-    feature = "rustls-0_22-native-roots",
-))]
+#[cfg(feature = "rustls-0_22")]
 pub mod rustls_0_22;
 
 #[cfg(feature = "native-tls")]
@@ -37,8 +34,7 @@ pub(crate) static MAX_CONN: AtomicUsize = AtomicUsize::new(256);
     feature = "openssl",
     feature = "rustls-0_20",
     feature = "rustls-0_21",
-    feature = "rustls-0_22-webpki-roots",
-    feature = "rustls-0_22-native-roots",
+    feature = "rustls-0_22",
     feature = "native-tls",
 ))]
 pub(crate) const DEFAULT_TLS_HANDSHAKE_TIMEOUT: std::time::Duration =
diff --git a/actix-tls/src/connect/mod.rs b/actix-tls/src/connect/mod.rs
index b742e76e..1e77d98a 100644
--- a/actix-tls/src/connect/mod.rs
+++ b/actix-tls/src/connect/mod.rs
@@ -46,10 +46,7 @@ pub use rustls_0_20 as rustls;
 ))]
 pub mod rustls_0_21;
 
-#[cfg(any(
-    feature = "rustls-0_22-webpki-roots",
-    feature = "rustls-0_22-native-roots",
-))]
+#[cfg(feature = "rustls-0_22")]
 pub mod rustls_0_22;
 
 #[cfg(feature = "native-tls")]
diff --git a/actix-tls/src/connect/rustls_0_20.rs b/actix-tls/src/connect/rustls_0_20.rs
index 19553044..fc65b686 100644
--- a/actix-tls/src/connect/rustls_0_20.rs
+++ b/actix-tls/src/connect/rustls_0_20.rs
@@ -3,7 +3,6 @@
 //! See [`TlsConnector`] for main connector service factory docs.
 
 use std::{
-    convert::TryFrom,
     future::Future,
     io,
     pin::Pin,
@@ -35,6 +34,8 @@ pub mod reexports {
 /// Returns root certificates via `rustls-native-certs` crate as a rustls certificate store.
 ///
 /// See [`rustls_native_certs::load_native_certs()`] for more info on behavior and errors.
+///
+/// [`rustls_native_certs::load_native_certs()`]: rustls_native_certs_06::load_native_certs()
 #[cfg(feature = "rustls-0_20-native-roots")]
 pub fn native_roots_cert_store() -> io::Result<RootCertStore> {
     let mut root_certs = RootCertStore::empty();
diff --git a/actix-tls/src/connect/rustls_0_21.rs b/actix-tls/src/connect/rustls_0_21.rs
index 7474327a..071cb98e 100644
--- a/actix-tls/src/connect/rustls_0_21.rs
+++ b/actix-tls/src/connect/rustls_0_21.rs
@@ -3,7 +3,6 @@
 //! See [`TlsConnector`] for main connector service factory docs.
 
 use std::{
-    convert::TryFrom,
     future::Future,
     io,
     pin::Pin,
@@ -35,6 +34,8 @@ pub mod reexports {
 /// Returns root certificates via `rustls-native-certs` crate as a rustls certificate store.
 ///
 /// See [`rustls_native_certs::load_native_certs()`] for more info on behavior and errors.
+///
+/// [`rustls_native_certs::load_native_certs()`]: rustls_native_certs_06::load_native_certs()
 #[cfg(feature = "rustls-0_21-native-roots")]
 pub fn native_roots_cert_store() -> io::Result<RootCertStore> {
     let mut root_certs = RootCertStore::empty();
diff --git a/actix-tls/src/connect/rustls_0_22.rs b/actix-tls/src/connect/rustls_0_22.rs
index bb6f5f83..7db1c7d8 100644
--- a/actix-tls/src/connect/rustls_0_22.rs
+++ b/actix-tls/src/connect/rustls_0_22.rs
@@ -16,9 +16,8 @@ use actix_utils::future::{ok, Ready};
 use futures_core::ready;
 use rustls_pki_types_1::ServerName;
 use tokio_rustls::{
-    client::TlsStream as AsyncTlsStream,
-    rustls::{ClientConfig, RootCertStore},
-    Connect as RustlsConnect, TlsConnector as RustlsTlsConnector,
+    client::TlsStream as AsyncTlsStream, rustls::ClientConfig, Connect as RustlsConnect,
+    TlsConnector as RustlsTlsConnector,
 };
 use tokio_rustls_025 as tokio_rustls;
 
@@ -35,9 +34,11 @@ pub mod reexports {
 /// Returns root certificates via `rustls-native-certs` crate as a rustls certificate store.
 ///
 /// See [`rustls_native_certs::load_native_certs()`] for more info on behavior and errors.
+///
+/// [`rustls_native_certs::load_native_certs()`]: rustls_native_certs_07::load_native_certs()
 #[cfg(feature = "rustls-0_22-native-roots")]
-pub fn native_roots_cert_store() -> io::Result<RootCertStore> {
-    let mut root_certs = RootCertStore::empty();
+pub fn native_roots_cert_store() -> io::Result<tokio_rustls::rustls::RootCertStore> {
+    let mut root_certs = tokio_rustls::rustls::RootCertStore::empty();
 
     for cert in rustls_native_certs_07::load_native_certs()? {
         root_certs.add(cert).unwrap();
@@ -48,8 +49,8 @@ pub fn native_roots_cert_store() -> io::Result<RootCertStore> {
 
 /// Returns standard root certificates from `webpki-roots` crate as a rustls certificate store.
 #[cfg(feature = "rustls-0_22-webpki-roots")]
-pub fn webpki_roots_cert_store() -> RootCertStore {
-    let mut root_certs = RootCertStore::empty();
+pub fn webpki_roots_cert_store() -> tokio_rustls::rustls::RootCertStore {
+    let mut root_certs = tokio_rustls::rustls::RootCertStore::empty();
     root_certs.extend(webpki_roots_026::TLS_SERVER_ROOTS.to_owned());
     root_certs
 }
diff --git a/actix-tls/tests/accept-openssl.rs b/actix-tls/tests/accept-openssl.rs
index a3506b1b..e571a282 100644
--- a/actix-tls/tests/accept-openssl.rs
+++ b/actix-tls/tests/accept-openssl.rs
@@ -12,11 +12,13 @@ use std::{io::Write as _, sync::Arc};
 use actix_rt::net::TcpStream;
 use actix_server::TestServer;
 use actix_service::ServiceFactoryExt as _;
-use actix_tls::accept::openssl::{Acceptor, TlsStream};
+use actix_tls::{
+    accept::openssl::{Acceptor, TlsStream},
+    connect::rustls_0_22::reexports::ClientConfig,
+};
 use actix_utils::future::ok;
 use rustls_pki_types_1::ServerName;
-use tokio_rustls::rustls::{ClientConfig, RootCertStore};
-use tokio_rustls_025 as tokio_rustls;
+use tokio_rustls_025::rustls::RootCertStore;
 
 fn new_cert_and_key() -> (String, String) {
     let cert =
@@ -47,39 +49,40 @@ fn openssl_acceptor(cert: String, key: String) -> tls_openssl::ssl::SslAcceptor
     builder.build()
 }
 
-#[allow(dead_code)]
 mod danger {
+    use rustls_pki_types_1::{CertificateDer, ServerName, UnixTime};
     use tokio_rustls_025::rustls;
 
+    /// Disables certificate verification to allow self-signed certs from rcgen.
     #[derive(Debug)]
     pub struct NoCertificateVerification;
 
     impl rustls::client::danger::ServerCertVerifier for NoCertificateVerification {
         fn verify_server_cert(
             &self,
-            end_entity: &rustls_pki_types_1::CertificateDer::CertificateDer<'_>,
-            intermediates: &[rustls_pki_types_1::CertificateDer::CertificateDer<'_>],
-            server_name: &rustls_pki_types_1::CertificateDer::ServerName<'_>,
-            ocsp_response: &[u8],
-            now: rustls_pki_types_1::CertificateDer::UnixTime,
+            _end_entity: &CertificateDer<'_>,
+            _intermediates: &[CertificateDer<'_>],
+            _server_name: &ServerName<'_>,
+            _ocsp_response: &[u8],
+            _now: UnixTime,
         ) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
             Ok(rustls::client::danger::ServerCertVerified::assertion())
         }
 
         fn verify_tls12_signature(
             &self,
-            message: &[u8],
-            cert: &rustls_pki_types_1::CertificateDer<'_>,
-            dss: &rustls::DigitallySignedStruct,
+            _message: &[u8],
+            _cert: &rustls_pki_types_1::CertificateDer<'_>,
+            _dss: &rustls::DigitallySignedStruct,
         ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
             Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
         }
 
         fn verify_tls13_signature(
             &self,
-            message: &[u8],
-            cert: &rustls_pki_types_1::CertificateDer<'_>,
-            dss: &rustls::DigitallySignedStruct,
+            _message: &[u8],
+            _cert: &rustls_pki_types_1::CertificateDer<'_>,
+            _dss: &rustls::DigitallySignedStruct,
         ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
             Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
         }
@@ -134,13 +137,13 @@ async fn accepts_connections() {
     let config = rustls_connector(cert, key);
     let config = Arc::new(config);
 
-    let mut conn = tokio_rustls::rustls::ClientConnection::new(
+    let mut conn = tokio_rustls_025::rustls::ClientConnection::new(
         config,
         ServerName::try_from("localhost").unwrap(),
     )
     .unwrap();
 
-    let mut stream = tokio_rustls::rustls::Stream::new(&mut conn, &mut sock);
+    let mut stream = tokio_rustls_025::rustls::Stream::new(&mut conn, &mut sock);
 
     stream.flush().expect("TLS handshake failed");
 }
diff --git a/actix-tls/tests/accept-rustls.rs b/actix-tls/tests/accept-rustls.rs
index 554f0fc1..bed2f6c3 100644
--- a/actix-tls/tests/accept-rustls.rs
+++ b/actix-tls/tests/accept-rustls.rs
@@ -15,14 +15,13 @@ use actix_rt::net::TcpStream;
 use actix_server::TestServer;
 use actix_service::ServiceFactoryExt as _;
 use actix_tls::{
-    accept::rustls_0_21::{Acceptor, TlsStream},
+    accept::rustls_0_22::{reexports::ServerConfig, Acceptor, TlsStream},
     connect::openssl::reexports::SslConnector,
 };
 use actix_utils::future::ok;
 use rustls_pemfile::{certs, pkcs8_private_keys};
+use rustls_pki_types_1::PrivateKeyDer;
 use tls_openssl::ssl::SslVerifyMode;
-use tokio_rustls::rustls::{self, Certificate, PrivateKey, ServerConfig};
-use tokio_rustls_024 as tokio_rustls;
 
 fn new_cert_and_key() -> (String, String) {
     let cert =
@@ -35,7 +34,7 @@ fn new_cert_and_key() -> (String, String) {
     (cert, key)
 }
 
-fn rustls_server_config(cert: String, key: String) -> rustls::ServerConfig {
+fn rustls_server_config(cert: String, key: String) -> ServerConfig {
     // Load TLS key and cert files
 
     let cert = &mut BufReader::new(cert.as_bytes());
@@ -47,9 +46,8 @@ fn rustls_server_config(cert: String, key: String) -> rustls::ServerConfig {
         .unwrap();
 
     let mut config = ServerConfig::builder()
-        .with_safe_defaults()
         .with_no_client_auth()
-        .with_single_cert(cert_chain, PrivateKey(keys.remove(0)))
+        .with_single_cert(cert_chain, PrivateKeyDer::Pkcs8(keys.remove(0)))
         .unwrap();
 
     config.alpn_protocols = vec![b"http/1.1".to_vec()];
diff --git a/actix-tls/tests/test_connect.rs b/actix-tls/tests/test_connect.rs
index d3a68276..820df7e3 100644
--- a/actix-tls/tests/test_connect.rs
+++ b/actix-tls/tests/test_connect.rs
@@ -98,8 +98,6 @@ async fn service_factory() {
 #[cfg(all(feature = "openssl", feature = "uri"))]
 #[actix_rt::test]
 async fn test_openssl_uri() {
-    use std::convert::TryFrom;
-
     let srv = TestServer::start(|| {
         fn_service(|io: TcpStream| async {
             let mut framed = Framed::new(io, BytesCodec);
@@ -134,8 +132,6 @@ async fn test_rustls_uri_http1() {
 #[cfg(all(feature = "rustls-0_22", feature = "uri"))]
 #[actix_rt::test]
 async fn test_rustls_uri() {
-    use std::convert::TryFrom;
-
     let srv = TestServer::start(|| {
         fn_service(|io: TcpStream| async {
             let mut framed = Framed::new(io, BytesCodec);
diff --git a/actix-tracing/CHANGES.md b/actix-tracing/CHANGES.md
index 2e90e25c..15f1437a 100644
--- a/actix-tracing/CHANGES.md
+++ b/actix-tracing/CHANGES.md
@@ -2,7 +2,7 @@
 
 ## Unreleased
 
-- Minimum supported Rust version (MSRV) is now 1.65.
+- Minimum supported Rust version (MSRV) is now 1.70.
 
 ## 0.1.0
 
diff --git a/actix-tracing/Cargo.toml b/actix-tracing/Cargo.toml
index 0c64e9ab..6e06beed 100644
--- a/actix-tracing/Cargo.toml
+++ b/actix-tracing/Cargo.toml
@@ -5,7 +5,7 @@ authors = ["Rajasekharan Vengalil <avranju@gmail.com>"]
 description = "Support for tokio tracing with Actix services"
 keywords = ["network", "framework", "tracing"]
 homepage = "https://actix.rs"
-repository = "https://github.com/actix/actix-net.git"
+repository = "https://github.com/actix/actix-net/tree/master/actix-tracing"
 documentation = "https://docs.rs/actix-tracing"
 categories = ["network-programming", "asynchronous"]
 license = "MIT OR Apache-2.0"
@@ -27,6 +27,6 @@ actix-utils = "3"
 tracing = "0.1.35"
 tracing-futures = "0.2"
 
-[dev_dependencies]
+[dev-dependencies]
 actix-rt = "2"
 slab = "0.4"
diff --git a/actix-tracing/src/lib.rs b/actix-tracing/src/lib.rs
index b1b9061b..4107b429 100644
--- a/actix-tracing/src/lib.rs
+++ b/actix-tracing/src/lib.rs
@@ -131,7 +131,7 @@ mod test {
     use super::*;
 
     thread_local! {
-        static SPAN: RefCell<Vec<span::Id>> = RefCell::new(Vec::new());
+        static SPAN: RefCell<Vec<span::Id>> = const { RefCell::new(Vec::new()) };
     }
 
     #[derive(Default)]
diff --git a/actix-utils/CHANGES.md b/actix-utils/CHANGES.md
index c68c3171..de510788 100644
--- a/actix-utils/CHANGES.md
+++ b/actix-utils/CHANGES.md
@@ -2,7 +2,7 @@
 
 ## Unreleased
 
-- Minimum supported Rust version (MSRV) is now 1.65.
+- Minimum supported Rust version (MSRV) is now 1.70.
 
 ## 3.0.1
 
diff --git a/actix-utils/src/future/poll_fn.rs b/actix-utils/src/future/poll_fn.rs
index 0c798f46..09638bcf 100644
--- a/actix-utils/src/future/poll_fn.rs
+++ b/actix-utils/src/future/poll_fn.rs
@@ -103,6 +103,7 @@ mod tests {
     #[allow(dead_code)]
     fn require_sync<T: Sync>(_t: &T) {}
 
+    #[allow(unused)]
     trait AmbiguousIfUnpin<A> {
         fn some_item(&self) {}
     }
diff --git a/bytestring/CHANGES.md b/bytestring/CHANGES.md
index 368ead4d..8a24680f 100644
--- a/bytestring/CHANGES.md
+++ b/bytestring/CHANGES.md
@@ -2,6 +2,8 @@
 
 ## Unreleased
 
+- Minimum supported Rust version (MSRV) is now 1.70.
+
 ## 1.3.1
 
 - No significant changes since `1.3.0`.
diff --git a/bytestring/src/lib.rs b/bytestring/src/lib.rs
index 80ab085e..6cb164dc 100644
--- a/bytestring/src/lib.rs
+++ b/bytestring/src/lib.rs
@@ -13,7 +13,7 @@ use alloc::{
     string::{String, ToString},
     vec::Vec,
 };
-use core::{borrow::Borrow, convert::TryFrom, fmt, hash, ops, str};
+use core::{borrow::Borrow, fmt, hash, ops, str};
 
 use bytes::Bytes;
 
diff --git a/justfile b/justfile
index 86bd8412..c970a73c 100644
--- a/justfile
+++ b/justfile
@@ -1,14 +1,62 @@
 _list:
     @just --list
 
+# Downgrade dev-dependencies necessary to run MSRV checks/tests.
+[private]
+downgrade-for-msrv:
+    cargo update -p=ciborium --precise=0.2.1
+    cargo update -p=ciborium-ll --precise=0.2.1
+    cargo update -p=time --precise=0.3.16
+    cargo update -p=clap --precise=4.3.24
+    cargo update -p=clap_lex --precise=0.5.0
+    cargo update -p=anstyle --precise=1.0.2
+    cargo update -p=trybuild --precise=1.0.89
+
+msrv := ```
+    cargo metadata --format-version=1 \
+    | jq -r 'first(.packages[] | select(.source == null and .name == "actix-tls")) | .rust_version'
+```
+msrv_full := msrv + ".0" # comment out if the MSRV has a patch version specified
+msrv_rustup := "+" + msrv_full
+
+non_linux_all_features_list := ```
+    cargo metadata --format-version=1 \
+    | jq '.packages[] | select(.source == null) | .features | keys' \
+    | jq -r --slurp \
+        --arg exclusions "tokio-uring,io-uring" \
+        'add | unique | . - ($exclusions | split(",")) | join(",")'
+```
+
+all_crate_features := if os() == "linux" {
+    "--all-features"
+} else {
+    "--features='" + non_linux_all_features_list + "'"
+}
+
+# Test workspace code.
+test toolchain="":
+    cargo {{ toolchain }} test --lib --tests --package=actix-macros
+    cargo {{ toolchain }} nextest run --workspace --exclude=actix-macros --no-default-features
+    cargo {{ toolchain }} nextest run --workspace --exclude=actix-macros {{ all_crate_features }}
+
+# Test workspace using MSRV.
+test-msrv: downgrade-for-msrv (test msrv_rustup)
+
+# Test workspace docs.
+test-docs toolchain="": && doc
+    cargo {{ toolchain }} test --doc --workspace {{ all_crate_features }} --no-fail-fast -- --nocapture
+
+# Test workspace.
+test-all toolchain="": (test toolchain) (test-docs)
+
 # Document crates in workspace.
-doc:
-    RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly doc --no-deps --workspace --features=rustls,openssl
+doc *args:
+    RUSTDOCFLAGS="--cfg=docsrs -Dwarnings" cargo +nightly doc --no-deps --workspace {{ all_crate_features }} {{ args }}
 
 # Document crates in workspace and watch for changes.
 doc-watch:
-    RUSTDOCFLAGS="--cfg=docsrs"                cargo +nightly doc --no-deps --workspace --features=rustls-0_20,rustls-0_21,rustls-0_20-native-roots,rustls-0_21-native-roots,openssl --open
-    cargo watch -- RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly doc --no-deps --workspace --features=rustls-0_20,rustls-0_21,rustls-0_20-native-roots,rustls-0_21-native-roots,openssl
+    @just doc --open
+    cargo watch -- just doc
 
 # Check for unintentional external type exposure on all crates in workspace.
 check-external-types-all toolchain="+nightly":
@@ -16,7 +64,7 @@ check-external-types-all toolchain="+nightly":
     set -euo pipefail
     exit=0
     for f in $(find . -mindepth 2 -maxdepth 2 -name Cargo.toml | grep -vE "\-codegen/|\-derive/|\-macros/"); do
-        if ! just check-external-types-manifest "$f" {{toolchain}}; then exit=1; fi
+        if ! just check-external-types-manifest "$f" {{ toolchain }}; then exit=1; fi
         echo
         echo
     done
@@ -29,9 +77,9 @@ check-external-types-all-table toolchain="+nightly":
     for f in $(find . -mindepth 2 -maxdepth 2 -name Cargo.toml | grep -vE "\-codegen/|\-derive/|\-macros/"); do
         echo
         echo "Checking for $f"
-        just check-external-types-manifest "$f" {{toolchain}} --output-format=markdown-table
+        just check-external-types-manifest "$f" {{ toolchain }} --output-format=markdown-table
     done
 
 # Check for unintentional external type exposure on a crate.
 check-external-types-manifest manifest_path toolchain="+nightly" *extra_args="":
-    cargo {{toolchain}} check-external-types --manifest-path "{{manifest_path}}" {{extra_args}}
+    cargo {{ toolchain }} check-external-types --manifest-path "{{ manifest_path }}" {{ extra_args }}
diff --git a/local-channel/CHANGES.md b/local-channel/CHANGES.md
index 3244f033..602d09b3 100644
--- a/local-channel/CHANGES.md
+++ b/local-channel/CHANGES.md
@@ -2,6 +2,8 @@
 
 ## Unreleased
 
+- Minimum supported Rust version (MSRV) is now 1.70.
+
 ## 0.1.5
 
 - No significant changes since `0.1.4`.
diff --git a/local-waker/CHANGES.md b/local-waker/CHANGES.md
index c83d06d4..a6adfbba 100644
--- a/local-waker/CHANGES.md
+++ b/local-waker/CHANGES.md
@@ -2,6 +2,8 @@
 
 ## Unreleased
 
+- Minimum supported Rust version (MSRV) is now 1.70.
+
 ## 0.1.4
 
 - Minimum supported Rust version (MSRV) is now 1.65.