Upgrading old Apache 2.2 configurations to httpd 2.4

Updated November 2026

Apache httpd 2.2 is end-of-life: its final release, 2.2.34, came out in July 2017 and gets no more security fixes. Most 2.2 configuration files run on 2.4 after a handful of edits, and nearly all of them are in one place: access control, where Order, Allow and Deny give way to Require.

Plan the move

Few people compile a new 2.4 over a running 2.2. A typical upgrade means an old server is being replaced by a current distribution that ships 2.4, and its configuration has to come along. Before you copy anything, record what the old server actually does:

# build settings, MPM, default paths (on old Debian: apache2ctl -V)
httpd -V
# loaded modules
httpd -M
# virtual hosts and which config file defines each one
httpd -S

Then work through the changes below on a test machine or a second virtual host. Third-party modules compiled for 2.2 must be rebuilt for 2.4 before they will load. Many work unchanged once recompiled, but some need code changes, so get each one from your distribution or its project in a 2.4 build.

A clean approach: start from the 2.4 default configuration your package installs, then port your virtual hosts and custom settings into it one at a time. You avoid dragging along 2.2 defaults that no longer exist.

Access control: Order, Allow, Deny

In 2.2, host-based access used Order, Allow and Deny (module mod_authz_host, called mod_access before 2.2), and Satisfy combined them with password checks. In 2.4 the same checks are authorization providers of the Require directive (modules mod_authz_core and mod_authz_host). The old directives survive only in mod_access_compat, which is marked deprecated.

Old syntax you may still see, and its 2.4 replacement
2.22.4
Order deny,allow
Deny from all
Require all denied
Order allow,deny
Allow from all
Require all granted
Order Deny,Allow
Deny from all
Allow from example.org
Require host example.org
Allow from 192.168.1Require ip 192.168.1
Allow from 10.0.0.0/8Require ip 10.0.0.0/8
Allow from 127.0.0.1 ::1Require local (127.0.0.0/8, ::1, or same address as the server)

To block a few addresses and let everyone else in, combine rules in a <RequireAll> block. Several Require lines with no container around them act like <RequireAny>: one match is enough.

<Directory "/var/www/example">
    <RequireAll>
        Require all granted
        Require not ip 198.51.100.0/24
    </RequireAll>
</Directory>

Passwords plus addresses (Satisfy)

Where 2.2 used Order allow,deny + Allow from all only to switch host checks off before a password prompt, just delete those lines. Where both checks really mattered, translate Satisfy:

# 2.2 "Satisfy All": a valid password AND an office address
AuthType Basic
AuthName "Staff only"
AuthBasicProvider file
AuthUserFile "/etc/apache2/.htpasswd"
<RequireAll>
    Require valid-user
    Require ip 192.0.2.0/24
</RequireAll>

# 2.2 "Satisfy Any": a valid password OR an office address
AuthType Basic
AuthName "Staff only"
AuthBasicProvider file
AuthUserFile "/etc/apache2/.htpasswd"
Require valid-user
Require ip 192.0.2.0/24

On RHEL and Fedora the password file would normally live under /etc/httpd/. The Authz*Authoritative directives were removed; RequireAll, RequireAny and RequireNone replace them. If you used mod_authz_dbm, write Require dbm-group instead of Require group.

Do not mix old and new

Debian and Ubuntu enable mod_access_compat on a fresh install, and Fedora/RHEL load it in /etc/httpd/conf.modules.d/00-base.conf. That is why an unconverted 2.2 file often starts without errors. The trap comes when old and new directives meet in the merge order. The 2.4 upgrade notes show a case where Deny from all on <Directory "/"> overrides Require local on /server-status: you get a 403, and error_log logs AH01797 ("client denied by server configuration").

Convert everything in one pass. Find the old lines:

grep -rniE '^\s*(order|allow from|deny from|satisfy)\b' /etc/apache2   # Debian/Ubuntu
grep -rniE '^\s*(order|allow from|deny from|satisfy)\b' /etc/httpd     # RHEL/Fedora

Remember .htaccess files under your document roots too. When grep finds nothing, unload the module so stray old lines fail loudly at startup instead of half-working:

sudo a2dismod access_compat          # Debian/Ubuntu
# RHEL/Fedora: comment out the access_compat LoadModule line in
# /etc/httpd/conf.modules.d/00-base.conf

More examples are in the access control guide.

Removed and renamed directives

2.2 directives and what to do in 2.4
2.2 directiveIn 2.4
NameVirtualHostNo effect; prints warning AH00548. Delete it: any address/port used by several virtual hosts is name-based automatically.
MaxClientsRenamed MaxRequestWorkers (old name still accepted).
MaxRequestsPerChildRenamed MaxConnectionsPerChild (old name still accepted).
DefaultTypeOnly a warning (AH00117) unless set to none. Delete it; set types with AddType or ForceType.
AcceptMutex, LockFile, RewriteLock, SSLMutex, SSLStaplingMutex, WatchdogMutexPathReplaced by one Mutex directive. Often you can just delete them.
RewriteLog, RewriteLogLevelRemoved. Use LogLevel alert rewrite:trace3; output goes to error_log.
KeepAlive 5 or similarOnly On or Off is accepted now.
mod_mem_cacheRemoved; use mod_cache_disk.
AuthnProviderAlias (mod_authn_alias)Moved into mod_authn_core.
AddOutputFilterByTypeMoved from the core to mod_filter, which must be loaded.
DumpIOLogLevelRemoved; mod_dumpio logs at LogLevel trace7.

Lines that are now invalid stop the server with Invalid command 'RewriteLog', perhaps misspelled or defined by a module not included in the server configuration. That is useful: configtest finds them all for you, one per run.

Defaults that changed

MPM changes

In 2.4 the MPM (the module that decides how processes and threads handle connections) can be a loadable module, so you change it with LoadModule instead of recompiling. The event MPM, experimental in 2.2, is fully supported, and it is the default on fresh Debian installs and on current Fedora and RHEL packages. It hands idle keep-alive connections to a listener thread, so worker threads are not tied up waiting for them.

# Debian/Ubuntu: switch from prefork to event
sudo a2dismod mpm_prefork
sudo a2enmod mpm_event

# RHEL/Fedora: uncomment exactly one LoadModule line in
# /etc/httpd/conf.modules.d/00-mpm.conf

With a threaded MPM (event or worker), CGI runs through mod_cgid instead of mod_cgi; see CGI and SSI. Re-size MaxRequestWorkers, ThreadsPerChild and ServerLimit for the new MPM rather than copying 2.2 prefork numbers (performance tuning).

mod_ssl changes

A minimal 2.4 HTTPS virtual host:

<VirtualHost *:443>
    ServerName www.example.com
    DocumentRoot "/var/www/example"
    SSLEngine on
    SSLCertificateFile    "/etc/letsencrypt/live/www.example.com/fullchain.pem"
    SSLCertificateKeyFile "/etc/letsencrypt/live/www.example.com/privkey.pem"
    SSLProtocol -all +TLSv1.2 +TLSv1.3
</VirtualHost>

Cipher and header recommendations are in HTTPS and TLS.

Debian and Ubuntu specifics

On Debian-based systems the Include lines in apache2.conf only read files ending in .conf. A 2.2-era site file named /etc/apache2/sites-available/example is ignored until you rename it:

sudo mv /etc/apache2/sites-available/example /etc/apache2/sites-available/example.conf
sudo a2ensite example
sudo apache2ctl -t && sudo systemctl reload apache2

mod_disk_cache from 2.2 is called mod_cache_disk in 2.4. In 2.4, an Include with a wildcard that matches no file is an error; use IncludeOptional for directories that may be empty.

Configs from Apache 1.3 or 2.0

Very old files need the 2.2 changes above plus earlier ones. Work through them in order:

1.3 modules do not work on 2.x at all: the module API changed completely in 2.0.

Test, then switch

Run the config test after each batch of edits, and only reload when it says Syntax OK:

sudo apache2ctl -t && sudo systemctl reload apache2     # Debian/Ubuntu
sudo apachectl configtest && sudo systemctl reload httpd # RHEL/Fedora
sudo apachectl -S     # check that every vhost landed where you expect
Common errors after an upgrade
MessageFix
Invalid command 'Order' (or 'Allow', 'Deny')Convert to Require, or temporarily load mod_access_compat.
Invalid command 'Require'Load mod_authz_core and mod_authz_host.
Invalid command 'User'Load mod_unixd (compiled in on Debian packages).
Invalid command 'AddOutputFilterByType'Load mod_filter.
configuration error: couldn't check userLoad mod_authn_core.
.htaccess rules do nothingSet AllowOverride for that directory.
403 with AH01630 or AH01797 in error_logA Require rule (AH01630) or an old Deny (AH01797) refused the request; check the merge order of your sections.

Finally, compare responses between old and new servers for your important URLs (curl -I https://www.example.com/path) before moving DNS or the IP address.

Sources