diff --git a/.evergreen/config.yml b/.evergreen/config.yml
index 4562f1d2be..e5b640a890 100644
--- a/.evergreen/config.yml
+++ b/.evergreen/config.yml
@@ -227,17 +227,6 @@ functions:
         args:
           - ${DRIVERS_TOOLS}/.evergreen/atlas_data_lake/run-mongohouse-image.sh
 
-  "run mod_wsgi tests":
-    - command: subprocess.exec
-      type: test
-      params:
-        include_expansions_in_env: [MOD_WSGI_VERSION, MOD_WSGI_EMBEDDED, "PYTHON_BINARY"]
-        working_dir: "src"
-        binary: bash
-        args:
-          - .evergreen/scripts/run-with-env.sh
-          - .evergreen/scripts/run-mod-wsgi-tests.sh
-
   "run doctests":
      - command: subprocess.exec
        type: test
@@ -411,40 +400,6 @@ tasks:
             TEST_NAME: index_management
             AUTH: "auth"
 
-    - name: "mod-wsgi-standalone"
-      tags: ["mod_wsgi"]
-      commands:
-        - func: "run server"
-          vars:
-            TOPOLOGY: "server"
-        - func: "run mod_wsgi tests"
-
-    - name: "mod-wsgi-replica-set"
-      tags: ["mod_wsgi"]
-      commands:
-        - func: "run server"
-          vars:
-            TOPOLOGY: "replica_set"
-        - func: "run mod_wsgi tests"
-
-    - name: "mod-wsgi-embedded-mode-standalone"
-      tags: ["mod_wsgi"]
-      commands:
-        - func: "run server"
-        - func: "run mod_wsgi tests"
-          vars:
-            MOD_WSGI_EMBEDDED: "1"
-
-    - name: "mod-wsgi-embedded-mode-replica-set"
-      tags: ["mod_wsgi"]
-      commands:
-        - func: "run server"
-          vars:
-            TOPOLOGY: "replica_set"
-        - func: "run mod_wsgi tests"
-          vars:
-            MOD_WSGI_EMBEDDED: "1"
-
     - name: "no-server"
       tags: ["no-server"]
       commands:
diff --git a/.evergreen/generated_configs/tasks.yml b/.evergreen/generated_configs/tasks.yml
index 0b0f09329a..070b163e90 100644
--- a/.evergreen/generated_configs/tasks.yml
+++ b/.evergreen/generated_configs/tasks.yml
@@ -775,6 +775,48 @@ tasks:
           TEST_NAME: load_balancer
     tags: [load-balancer, noauth, nossl]
 
+  # Mod wsgi tests
+  - name: mod-wsgi-standalone
+    commands:
+      - func: run server
+        vars:
+          TOPOLOGY: standalone
+      - func: run tests
+        vars:
+          TEST_NAME: mod_wsgi
+          SUB_TEST_NAME: standalone
+    tags: [mod_wsgi]
+  - name: mod-wsgi-replica-set
+    commands:
+      - func: run server
+        vars:
+          TOPOLOGY: replica_set
+      - func: run tests
+        vars:
+          TEST_NAME: mod_wsgi
+          SUB_TEST_NAME: standalone
+    tags: [mod_wsgi]
+  - name: mod-wsgi-embedded-mode-standalone
+    commands:
+      - func: run server
+        vars:
+          TOPOLOGY: standalone
+      - func: run tests
+        vars:
+          TEST_NAME: mod_wsgi
+          SUB_TEST_NAME: embedded
+    tags: [mod_wsgi]
+  - name: mod-wsgi-embedded-mode-replica-set
+    commands:
+      - func: run server
+        vars:
+          TOPOLOGY: replica_set
+      - func: run tests
+        vars:
+          TEST_NAME: mod_wsgi
+          SUB_TEST_NAME: embedded
+    tags: [mod_wsgi]
+
   # Ocsp tests
   - name: test-ocsp-ecdsa-valid-cert-server-does-not-staple
     commands:
diff --git a/.evergreen/generated_configs/variants.yml b/.evergreen/generated_configs/variants.yml
index 4c54abf4b9..d70afa2bdd 100644
--- a/.evergreen/generated_configs/variants.yml
+++ b/.evergreen/generated_configs/variants.yml
@@ -696,10 +696,7 @@ buildvariants:
   # Mod wsgi tests
   - name: mod_wsgi-ubuntu-22-python3.9
     tasks:
-      - name: mod-wsgi-standalone
-      - name: mod-wsgi-replica-set
-      - name: mod-wsgi-embedded-mode-standalone
-      - name: mod-wsgi-embedded-mode-replica-set
+      - name: .mod_wsgi
     display_name: mod_wsgi Ubuntu-22 Python3.9
     run_on:
       - ubuntu2204-small
@@ -708,10 +705,7 @@ buildvariants:
       PYTHON_BINARY: /opt/python/3.9/bin/python3
   - name: mod_wsgi-ubuntu-22-python3.13
     tasks:
-      - name: mod-wsgi-standalone
-      - name: mod-wsgi-replica-set
-      - name: mod-wsgi-embedded-mode-standalone
-      - name: mod-wsgi-embedded-mode-replica-set
+      - name: .mod_wsgi
     display_name: mod_wsgi Ubuntu-22 Python3.13
     run_on:
       - ubuntu2204-small
diff --git a/.evergreen/scripts/generate_config.py b/.evergreen/scripts/generate_config.py
index d91e0e6ded..b90a6af437 100644
--- a/.evergreen/scripts/generate_config.py
+++ b/.evergreen/scripts/generate_config.py
@@ -614,12 +614,7 @@ def create_atlas_data_lake_variants():
 def create_mod_wsgi_variants():
     variants = []
     host = HOSTS["ubuntu22"]
-    tasks = [
-        "mod-wsgi-standalone",
-        "mod-wsgi-replica-set",
-        "mod-wsgi-embedded-mode-standalone",
-        "mod-wsgi-embedded-mode-replica-set",
-    ]
+    tasks = [".mod_wsgi"]
     expansions = dict(MOD_WSGI_VERSION="4")
     for python in MIN_MAX_PYTHON:
         display_name = get_display_name("mod_wsgi", host, python=python)
@@ -892,6 +887,24 @@ def create_oidc_tasks():
     return tasks
 
 
+def create_mod_wsgi_tasks():
+    tasks = []
+    for test, topology in product(["standalone", "embedded-mode"], ["standalone", "replica_set"]):
+        if test == "standalone":
+            task_name = "mod-wsgi-"
+        else:
+            task_name = "mod-wsgi-embedded-mode-"
+        task_name += topology.replace("_", "-")
+        server_vars = dict(TOPOLOGY=topology)
+        server_func = FunctionCall(func="run server", vars=server_vars)
+        vars = dict(TEST_NAME="mod_wsgi", SUB_TEST_NAME=test.split("-")[0])
+        test_func = FunctionCall(func="run tests", vars=vars)
+        tags = ["mod_wsgi"]
+        commands = [server_func, test_func]
+        tasks.append(EvgTask(name=task_name, tags=tags, commands=commands))
+    return tasks
+
+
 def _create_ocsp_task(algo, variant, server_type, base_task_name):
     file_name = f"{algo}-basic-tls-ocsp-{variant}.json"
 
diff --git a/.evergreen/scripts/mod_wsgi_tester.py b/.evergreen/scripts/mod_wsgi_tester.py
new file mode 100644
index 0000000000..5968849068
--- /dev/null
+++ b/.evergreen/scripts/mod_wsgi_tester.py
@@ -0,0 +1,93 @@
+from __future__ import annotations
+
+import os
+import sys
+import time
+import urllib.error
+import urllib.request
+from pathlib import Path
+from shutil import which
+
+from utils import LOGGER, ROOT, run_command, write_env
+
+
+def make_request(url, timeout=10):
+    for _ in range(int(timeout)):
+        try:
+            urllib.request.urlopen(url)  # noqa: S310
+            return
+        except urllib.error.HTTPError:
+            pass
+        time.sleep(1)
+    raise TimeoutError(f"Failed to access {url}")
+
+
+def setup_mod_wsgi(sub_test_name: str) -> None:
+    env = os.environ.copy()
+    if sub_test_name == "embedded":
+        env["MOD_WSGI_CONF"] = "mod_wsgi_test_embedded.conf"
+    elif sub_test_name == "standalone":
+        env["MOD_WSGI_CONF"] = "mod_wsgi_test.conf"
+    else:
+        raise ValueError("mod_wsgi sub test must be either 'standalone' or 'embedded'")
+    write_env("MOD_WSGI_CONF", env["MOD_WSGI_CONF"])
+    apache = which("apache2")
+    if not apache and Path("/usr/lib/apache2/mpm-prefork/apache2").exists():
+        apache = "/usr/lib/apache2/mpm-prefork/apache2"
+    if apache:
+        apache_config = "apache24ubuntu161404.conf"
+    else:
+        apache = which("httpd")
+        if not apache:
+            raise ValueError("Could not find apache2 or httpd")
+        apache_config = "apache22amazon.conf"
+    python_version = ".".join(str(val) for val in sys.version_info[:2])
+    mod_wsgi_version = 4
+    so_file = f"/opt/python/mod_wsgi/python_version/{python_version}/mod_wsgi_version/{mod_wsgi_version}/mod_wsgi.so"
+    write_env("MOD_WSGI_SO", so_file)
+    env["MOD_WSGI_SO"] = so_file
+    env["PYTHONHOME"] = f"/opt/python/{python_version}"
+    env["PROJECT_DIRECTORY"] = project_directory = str(ROOT)
+    write_env("APACHE_BINARY", apache)
+    write_env("APACHE_CONFIG", apache_config)
+    uri1 = f"http://localhost:8080/interpreter1{project_directory}"
+    write_env("TEST_URI1", uri1)
+    uri2 = f"http://localhost:8080/interpreter2{project_directory}"
+    write_env("TEST_URI2", uri2)
+    run_command(f"{apache} -k start -f {ROOT}/test/mod_wsgi_test/{apache_config}", env=env)
+
+    # Wait for the endpoints to be available.
+    try:
+        make_request(uri1, 10)
+        make_request(uri2, 10)
+    except Exception as e:
+        LOGGER.error(Path("error_log").read_text())
+        raise e
+
+
+def test_mod_wsgi() -> None:
+    sys.path.insert(0, ROOT)
+    from test.mod_wsgi_test.test_client import main, parse_args
+
+    uri1 = os.environ["TEST_URI1"]
+    uri2 = os.environ["TEST_URI2"]
+    args = f"-n 25000 -t 100 parallel {uri1} {uri2}"
+    try:
+        main(*parse_args(args.split()))
+
+        args = f"-n 25000 serial {uri1} {uri2}"
+        main(*parse_args(args.split()))
+    except Exception as e:
+        LOGGER.error(Path("error_log").read_text())
+        raise e
+
+
+def teardown_mod_wsgi() -> None:
+    apache = os.environ["APACHE_BINARY"]
+    apache_config = os.environ["APACHE_CONFIG"]
+
+    run_command(f"{apache} -k stop -f {ROOT}/test/mod_wsgi_test/{apache_config}")
+
+
+if __name__ == "__main__":
+    setup_mod_wsgi()
diff --git a/.evergreen/scripts/run-mod-wsgi-tests.sh b/.evergreen/scripts/run-mod-wsgi-tests.sh
deleted file mode 100755
index f59ace8116..0000000000
--- a/.evergreen/scripts/run-mod-wsgi-tests.sh
+++ /dev/null
@@ -1,53 +0,0 @@
-#!/bin/bash
-set -o xtrace
-set -o errexit
-
-APACHE=$(command -v apache2 || command -v /usr/lib/apache2/mpm-prefork/apache2) || true
-if [ -n "$APACHE" ]; then
-    APACHE_CONFIG=apache24ubuntu161404.conf
-else
-    APACHE=$(command -v httpd) || true
-    if [ -z "$APACHE" ]; then
-        echo "Could not find apache2 binary"
-        exit 1
-    else
-        APACHE_CONFIG=apache22amazon.conf
-    fi
-fi
-
-
-PYTHON_VERSION=$(${PYTHON_BINARY} -c "import sys; sys.stdout.write('.'.join(str(val) for val in sys.version_info[:2]))")
-
-# Ensure the C extensions are installed.
-${PYTHON_BINARY} -m venv --system-site-packages .venv
-source .venv/bin/activate
-pip install -U pip
-export PYMONGO_C_EXT_MUST_BUILD=1
-python -m pip install -v -e .
-
-export MOD_WSGI_SO=/opt/python/mod_wsgi/python_version/$PYTHON_VERSION/mod_wsgi_version/$MOD_WSGI_VERSION/mod_wsgi.so
-export PYTHONHOME=/opt/python/$PYTHON_VERSION
-# If MOD_WSGI_EMBEDDED is set use the default embedded mode behavior instead
-# of daemon mode (WSGIDaemonProcess).
-if [ -n "${MOD_WSGI_EMBEDDED:-}" ]; then
-    export MOD_WSGI_CONF=mod_wsgi_test_embedded.conf
-else
-    export MOD_WSGI_CONF=mod_wsgi_test.conf
-fi
-
-cd ..
-$APACHE -k start -f ${PROJECT_DIRECTORY}/test/mod_wsgi_test/${APACHE_CONFIG}
-trap '$APACHE -k stop -f ${PROJECT_DIRECTORY}/test/mod_wsgi_test/${APACHE_CONFIG}' EXIT HUP
-
-wget -t 1 -T 10 -O - "http://localhost:8080/interpreter1${PROJECT_DIRECTORY}" || (cat error_log && exit 1)
-wget -t 1 -T 10 -O - "http://localhost:8080/interpreter2${PROJECT_DIRECTORY}" || (cat error_log && exit 1)
-
-python ${PROJECT_DIRECTORY}/test/mod_wsgi_test/test_client.py -n 25000 -t 100 parallel \
-    http://localhost:8080/interpreter1${PROJECT_DIRECTORY} http://localhost:8080/interpreter2${PROJECT_DIRECTORY} || \
-    (tail -n 100 error_log && exit 1)
-
-python ${PROJECT_DIRECTORY}/test/mod_wsgi_test/test_client.py -n 25000 serial \
-    http://localhost:8080/interpreter1${PROJECT_DIRECTORY} http://localhost:8080/interpreter2${PROJECT_DIRECTORY} || \
-    (tail -n 100 error_log && exit 1)
-
-rm -rf .venv
diff --git a/.evergreen/scripts/run_tests.py b/.evergreen/scripts/run_tests.py
index 38fd3c67cb..13a510475f 100644
--- a/.evergreen/scripts/run_tests.py
+++ b/.evergreen/scripts/run_tests.py
@@ -100,6 +100,13 @@ def run() -> None:
     if TEST_PERF:
         start_time = datetime.now()
 
+    # Run mod_wsgi tests using the helper.
+    if TEST_NAME == "mod_wsgi":
+        from mod_wsgi_tester import test_mod_wsgi
+
+        test_mod_wsgi()
+        return
+
     # Send kms tests to run remotely.
     if TEST_NAME == "kms" and SUB_TEST_NAME in ["azure", "gcp"]:
         from kms_tester import test_kms_send_to_remote
diff --git a/.evergreen/scripts/setup_tests.py b/.evergreen/scripts/setup_tests.py
index 868ac419b5..fea397e642 100644
--- a/.evergreen/scripts/setup_tests.py
+++ b/.evergreen/scripts/setup_tests.py
@@ -251,6 +251,11 @@ def handle_test_env() -> None:
         cmd = f'bash "{DRIVERS_TOOLS}/.evergreen/run-load-balancer.sh" start'
         run_command(cmd)
 
+    if test_name == "mod_wsgi":
+        from mod_wsgi_tester import setup_mod_wsgi
+
+        setup_mod_wsgi(sub_test_name)
+
     if test_name == "ocsp":
         if sub_test_name:
             os.environ["OCSP_SERVER_TYPE"] = sub_test_name
@@ -378,7 +383,7 @@ def handle_test_env() -> None:
         # Use --capture=tee-sys so pytest prints test output inline:
         # https://docs.pytest.org/en/stable/how-to/capture-stdout-stderr.html
         TEST_ARGS = f"-v --capture=tee-sys --durations=5 {TEST_ARGS}"
-        TEST_SUITE = TEST_SUITE_MAP[test_name]
+        TEST_SUITE = TEST_SUITE_MAP.get(test_name)
         if TEST_SUITE:
             TEST_ARGS = f"-m {TEST_SUITE} {TEST_ARGS}"
 
diff --git a/.evergreen/scripts/teardown_tests.py b/.evergreen/scripts/teardown_tests.py
index 3920180422..750d2a0652 100644
--- a/.evergreen/scripts/teardown_tests.py
+++ b/.evergreen/scripts/teardown_tests.py
@@ -44,4 +44,10 @@
 elif TEST_NAME == "auth_aws" and sys.platform != "darwin":
     run_command(f"bash {DRIVERS_TOOLS}/.evergreen/auth_aws/teardown.sh")
 
+# Tear down mog_wsgi if applicable.
+elif TEST_NAME == "mod_wsgi":
+    from mod_wsgi_tester import teardown_mod_wsgi
+
+    teardown_mod_wsgi()
+
 LOGGER.info(f"Tearing down tests of type '{TEST_NAME}'... done.")
diff --git a/.evergreen/scripts/utils.py b/.evergreen/scripts/utils.py
index 08d376461e..45d01ae6bd 100644
--- a/.evergreen/scripts/utils.py
+++ b/.evergreen/scripts/utils.py
@@ -50,7 +50,9 @@ class Distro:
 }
 
 # Tests that require a sub test suite.
-SUB_TEST_REQUIRED = ["auth_aws", "auth_oidc", "kms"]
+SUB_TEST_REQUIRED = ["auth_aws", "auth_oidc", "kms", "mod_wsgi"]
+
+EXTRA_TESTS = ["mod_wsgi"]
 
 
 def get_test_options(
@@ -62,7 +64,7 @@ def get_test_options(
     if require_sub_test_name:
         parser.add_argument(
             "test_name",
-            choices=sorted(TEST_SUITE_MAP),
+            choices=sorted(list(TEST_SUITE_MAP) + EXTRA_TESTS),
             nargs="?",
             default="default",
             help="The optional name of the test suite to set up, typically the same name as a pytest marker.",
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8844565d31..d2a833d874 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -275,6 +275,17 @@ Note: these tests can only be run from an Evergreen host.
 - Run `just setup-tests atlas_connect`.
 - Run `just run-tests`.
 
+### mod_wsgi tests
+
+Note: these tests can only be run from an Evergreen Linux host that has the Python toolchain.
+
+- Run `just run-server`.
+- Run `just setup-tests mod_wsgi <mode>`.
+- Run `just run-tests`.
+
+The `mode` can be `standalone` or `embedded`.  For the `replica_set` version of the tests, use
+`TOPOLOGY=replica_set just run-server`.
+
 ### OCSP tests
 
   - Export the orchestration file, e.g. `export ORCHESTRATION_FILE=rsa-basic-tls-ocsp-disableStapling.json`.
diff --git a/test/mod_wsgi_test/test_client.py b/test/mod_wsgi_test/test_client.py
index 88eeb7a57e..c122863bfa 100644
--- a/test/mod_wsgi_test/test_client.py
+++ b/test/mod_wsgi_test/test_client.py
@@ -24,7 +24,7 @@
 from urllib.request import urlopen
 
 
-def parse_args():
+def parse_args(args=None):
     parser = OptionParser(
         """usage: %prog [options] mode url [<url2>...]
 
@@ -70,7 +70,7 @@ def parse_args():
     )
 
     try:
-        options, args = parser.parse_args()
+        options, args = parser.parse_args(args or sys.argv[1:])
         mode, urls = args[0], args[1:]
     except (ValueError, IndexError):
         parser.print_usage()
@@ -103,11 +103,11 @@ def __init__(self, options, urls, nrequests_per_thread):
     def run(self):
         for _i in range(self.nrequests_per_thread):
             try:
-                get(urls)
+                get(self.urls)
             except Exception as e:
                 print(e)
 
-                if not options.continue_:
+                if not self.options.continue_:
                     thread.interrupt_main()
                     thread.exit()
 
@@ -117,7 +117,7 @@ def run(self):
                 URLGetterThread.counter += 1
                 counter = URLGetterThread.counter
 
-            should_print = options.verbose and not counter % 1000
+            should_print = self.options.verbose and not counter % 1000
 
             if should_print:
                 print(counter)