Checking Your Installation

When debugging mod_wsgi or a WSGI application it is important to be able to confirm which Apache and which Python mod_wsgi is running against, how those installations were built, and what configuration the WSGI application is running under. This page lists the checks that gather that information.

The primary purpose of the page is to give a single reference for the diagnostic data that maintainers ask for when responding to a GitHub issue. If you have been directed here to collect information for a bug report, include the full output of the checks rather than a summary — partial output makes it harder to rule out causes.

Apache build information

The first piece of information to collect is which Apache binary is in use and how it was built. The starting point is httpd -V:

$ httpd -V
Server version: Apache/2.4.66 (Unix)
Server built:   Dec  1 2025 12:44:02
Server's Module Magic Number: 20120211:141
Server loaded:  APR 1.7.6, APR-UTIL 1.6.3, PCRE 10.47 2025-10-21
Compiled using: APR 1.7.6, APR-UTIL 1.6.3, PCRE 10.47 2025-10-21
Architecture:   64-bit
Server MPM:     prefork
  threaded:     no
    forked:     yes (variable process count)
Server compiled with....
 -D APR_HAS_SENDFILE
 -D APR_HAS_MMAP
 -D APR_HAVE_IPV6 (IPv4-mapped addresses enabled)
 -D APR_USE_SYSVSEM_SERIALIZE
 -D APR_USE_PTHREAD_SERIALIZE
 -D SINGLE_LISTEN_UNSERIALIZED_ACCEPT
 -D APR_HAS_OTHER_CHILD
 -D AP_HAVE_RELIABLE_PIPED_LOGS
 -D DYNAMIC_MODULE_LIMIT=256
 -D HTTPD_ROOT="/opt/homebrew/Cellar/httpd/2.4.66"
 -D SUEXEC_BIN="/opt/homebrew/opt/httpd/bin/suexec"
 -D DEFAULT_PIDLOG="/opt/homebrew/var/run/httpd/httpd.pid"
 -D DEFAULT_SCOREBOARD="logs/apache_runtime_status"
 -D DEFAULT_ERRORLOG="logs/error_log"
 -D AP_TYPES_CONFIG_FILE="/opt/homebrew/etc/httpd/mime.types"
 -D SERVER_CONFIG_FILE="/opt/homebrew/etc/httpd/httpd.conf"

The example above is from a Homebrew-installed Apache on macOS. The fields you should report when filing an issue are:

  • The Apache version, from Server version.

  • The MPM, from Server MPM. Common values are event (the Apache 2.4 default on most Linux distributions), worker, and prefork.

  • The architecture and APR/APR-UTIL versions, from the matching fields.

The location of the Apache executable depends on how Apache was installed: /usr/sbin/httpd (RHEL family), /usr/sbin/apache2 (Debian/Ubuntu), and /opt/homebrew/bin/httpd (Homebrew on Apple Silicon macOS) or /usr/local/bin/httpd (Homebrew on Intel macOS) are all common.

The -D block in the output is a curated subset of compile-time defines, not the full set of configure arguments. If you need the actual configure invocation that was used to build Apache, look for a config.nice file in Apache’s build directory. config.nice is generated alongside the build and recorded for reference. apxs -q installbuilddir reports where Apache thinks the build directory is:

$ apxs -q installbuilddir
/opt/homebrew/opt/httpd/lib/httpd/build
$ cat /opt/homebrew/opt/httpd/lib/httpd/build/config.nice

Apache modules loaded

Apache modules can be statically compiled into the httpd binary or loaded dynamically from configuration. httpd -l lists the statically compiled set:

$ httpd -l
Compiled in modules:
  core.c
  mod_so.c
  http_core.c

On almost all modern Apache builds this is a small fixed set including mod_so.c, which is the module that loads other modules dynamically.

httpd -M lists every module that will be loaded for the current configuration — both the statically compiled modules and any LoadModule directives in the active config files:

$ httpd -M
Loaded Modules:
 core_module (static)
 so_module (static)
 http_module (static)
 mpm_prefork_module (shared)
 authn_file_module (shared)
 authn_core_module (shared)
 authz_host_module (shared)
 authz_groupfile_module (shared)
 authz_user_module (shared)
 authz_core_module (shared)
 access_compat_module (shared)
 auth_basic_module (shared)
 reqtimeout_module (shared)
 filter_module (shared)
 mime_module (shared)
 log_config_module (shared)
 env_module (shared)
 headers_module (shared)
 setenvif_module (shared)
 version_module (shared)
 unixd_module (shared)
 status_module (shared)
 autoindex_module (shared)
 dir_module (shared)
 alias_module (shared)
 wsgi_module (shared)

The names match what would be used in a LoadModule directive, not the module file names on disk. The order in which modules appear matters when two modules can both handle a request and neither explicitly designates a relative dispatch order.

Confirm that wsgi_module is present in the list. If it isn’t, either the LoadModule directive is missing or Apache is rejecting the load — check the Apache error log for a load-time diagnostic.

Cross-process mutex

Apache uses a global cross-process mutex to serialise which child process accepts the next incoming connection. mod_wsgi separately uses a similar mutex per daemon process group to serialise which daemon-process worker accepts the next request proxied to the group. The mutex mechanism — flock, fcntl, sysvsem, posixsem, or pthread — varies by platform and can be overridden in configuration.

Under normal circumstances the platform default is fine and you should not need to override it. The information becomes relevant when reporting an issue that involves stalls or deadlocks between Apache children and mod_wsgi daemon workers.

Which mechanism Apache uses by default for the accept mutex can be read from the -D block in the httpd -V output above — the APR_USE_*_SERIALIZE lines describe the platform default. In the example above the lines are:

-D APR_USE_SYSVSEM_SERIALIZE
-D APR_USE_PTHREAD_SERIALIZE

The USE lines are interpreted in order, so this example shows sysvsem as the default. mod_wsgi uses the same default for its daemon-process-group mutex.

The default can be overridden for Apache itself via the Mutex directive (Apache 2.4 replaced the older AcceptMutex directive with a more general Mutex directive that takes a mutex name). For mod_wsgi’s daemon-process-group mutex, the override directive is WSGIAcceptMutex instead; Mutex does not affect mod_wsgi.

The set of mechanisms available on a given platform is whichever APR_HAS_*_SERIALIZE macros are defined to 1 in apr.h. The simplest way to discover the available set without locating apr.h is to put a deliberately invalid value into the configuration and run httpd -t:

$ httpd -t
AH00526: Syntax error on line N of /path/to/httpd.conf:
Invalid Mutex argument xxx (Mutex mechanisms are: 'none', \
 'default', 'flock:/path/to/file', 'fcntl:/path/to/file', \
 'file:/path/to/file', 'sysvsem', 'posixsem', 'sem')

The same trick works for WSGIAcceptMutex once mod_wsgi is loaded:

httpd: Syntax error on line N of /path/to/httpd.conf: \
Accept mutex lock mechanism 'xxx' is invalid. Valid accept \
mutex mechanisms for this platform are: default, flock, \
fcntl, sysvsem, posixsem.

default defers the choice back to APR.

Python shared library

mod_wsgi must be linked against Python via a shared library, not a static one. A normally-built mod_wsgi.so is well under 1MB and will show a libpython3.X.so line in ldd output on Linux:

$ ldd mod_wsgi.so
 linux-vdso.so.1 =>  (0x00007fffeb3fe000)
 libpython3.12.so.1.0 => /usr/local/lib/libpython3.12.so.1.0 (0x00002adebf94d000)
 libpthread.so.0 => /lib/libpthread.so.0 (0x00002adebfcba000)
 libdl.so.2 => /lib/libdl.so.2 (0x00002adebfed6000)
 libutil.so.1 => /lib/libutil.so.1 (0x00002adec00da000)
 libc.so.6 => /lib/libc.so.6 (0x00002adec02dd000)
 libm.so.6 => /lib/libm.so.6 (0x00002adec0635000)
 /lib64/ld-linux-x86-64.so.2 (0x0000555555554000)

When checking, unset LD_LIBRARY_PATH first — ldd honours it but Apache does not normally inherit it, so a check that relies on LD_LIBRARY_PATH may give a misleadingly clean result that doesn’t match what Apache will see at startup.

On macOS, ldd is not available; use otool -L instead:

$ otool -L mod_wsgi.so
mod_wsgi.so:
  /opt/homebrew/opt/python@3.12/Frameworks/Python.framework/Versions/3.12/Python (compatibility version 3.12.0, current version 3.12.10)
  /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1351.0.0)

If no libpython3.X.so (or framework) line appears, mod_wsgi was built against a static Python library and a number of follow-on issues apply. If the library appears but Apache fails to load mod_wsgi.so, or appears but resolves to the wrong copy, that is a separate runtime problem with its own workarounds. See Installation Issues for the full set of failure modes (“Lack Of Python Shared Library”, “Unable To Find Python Shared Library”, and the related sections).

Python installation in use

Even when mod_wsgi.so links to the right libpython, the Python interpreter still needs to find the installation — the matching lib/pythonX.Y/ directory containing the standard library and any installed packages.

The embedded Python locates this by searching PATH for its own executable name and taking the parent of where it is found as sys.prefix. Apache typically inherits a minimal PATH, so on hosts with multiple Python installations the wrong one can be picked up.

The most reliable way to confirm what Python is actually in use at runtime is to deploy the diagnostic WSGI script below and look at the sys.prefix and sys.version lines.

If the discovered installation is wrong, override the choice with the WSGIPythonHome directive, pointing at the sys.prefix of the Python you want used:

>>> import sys
>>> print(sys.prefix)
/usr/local
WSGIPythonHome /usr/local

The full discussion of multi-Python hosts and how the embedded Python finds its installation is in Installation Issues under “Multiple Python Versions”.

Diagnostic WSGI script

The remaining checks — what process group, application group, and threading model the WSGI application is running under, plus the Python identity values from the previous section — can all be captured with one diagnostic WSGI script:

import sys

def application(environ, start_response):
    lines = [
        "mod_wsgi.process_group     = %r" % environ["mod_wsgi.process_group"],
        "mod_wsgi.application_group = %r" % environ["mod_wsgi.application_group"],
        "wsgi.multithread           = %r" % environ["wsgi.multithread"],
        "wsgi.multiprocess          = %r" % environ["wsgi.multiprocess"],
        "sys.version                = %r" % sys.version,
        "sys.prefix                 = %r" % sys.prefix,
        "sys.path                   = %r" % sys.path,
    ]
    body = ("\n".join(lines) + "\n").encode("utf-8")
    start_response("200 OK", [
        ("Content-Type", "text/plain"),
        ("Content-Length", str(len(body))),
    ])
    return [body]

Mount the script and visit its URL. The output values are interpreted as follows.

mod_wsgi.process_group — name of the daemon process group the request was dispatched to. An empty string means embedded mode (the WSGI application is running inside an Apache child process, not a mod_wsgi daemon process). A non-empty string is the name of the daemon process group named by the active WSGIDaemonProcess / WSGIProcessGroup configuration. Embedded mode is also what WSGIProcessGroup %{GLOBAL} selects explicitly.

mod_wsgi.application_group — name of the Python sub-interpreter the application is running in. An empty string means the main interpreter (%{GLOBAL}). The default when WSGIApplicationGroup is not set is %{RESOURCE}, which produces a value composed from the server name, the connection port (omitted for ports 80 and 443), and the WSGI mount point — for example:

mod_wsgi.application_group = 'tests.example.com|/interpreter.wsgi'

wsgi.multithreadTrue if the WSGI application is running in a multithreaded environment, False if not. Daemon mode defaults to multithreaded. Embedded mode is multithreaded under the Event and Worker MPMs and single-threaded under the Prefork MPM. If True, the application code and any framework it uses must be thread-safe.

wsgi.multiprocessTrue if multiple processes may be serving requests for the application. Daemon mode is multiprocess when processes=N is set with N>1, otherwise single-process. Embedded mode is always multiprocess (each Apache child handles requests independently). Application-state caches that need to be shared across requests must allow for both multithread and multiprocess being True simultaneously.

sys.version, sys.prefix, sys.path — the Python installation and module search path actually in use at runtime, as discussed in the previous section.

Where to go next