Improving Apache Configuration Security

                
                Improving Apache Configuration Security
The article details practical methods for hardening Apache to reduce the attack surface. It lists key measures: limiting loaded modules, applying a “deny by default” policy, disabling directory listing and .htaccess, turning off CGI, hiding the server version and unnecessary methods, setting strict security headers, filtering suspicious requests and limiting packet sizes, configuring TLS sessions and enforcing HTTPS redirects, as well as careful log management and removal of redundant headers. This is a supplement to a post about deploying a Django project, but it is not directly related to Django—it focuses on security improvements specifically for Apache configuration.

1. Control of the module set (reduce the attack surface)

 

Every loaded module is a new potential attack vector (a vulnerability in the module itself, or its misconfiguration). The fewer modules — the fewer holes.

 

Historically, mod_status, mod_autoindex, CGI, and others “got hit”. With extra modules you can: reveal server internals, bypass authorization, get RCE in a chain with a vulnerable app/interpreter, or simply expand the DoS surface.

 

Principle of minimizing the Trusted Computing Base (TCB). The less code runs with the web server’s privileges, the lower the chance of a vulnerability and the shorter the exploitation chain.

 

LoadModule access_compat_module modules/mod_access_compat.so
LoadModule actions_module        modules/mod_actions.so
LoadModule alias_module          modules/mod_alias.so
LoadModule allowmethods_module   modules/mod_allowmethods.so
LoadModule auth_basic_module     modules/mod_auth_basic.so
LoadModule authn_core_module     modules/mod_authn_core.so
LoadModule authn_file_module     modules/mod_authn_file.so
LoadModule authz_core_module     modules/mod_authz_core.so
LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
LoadModule authz_host_module     modules/mod_authz_host.so
LoadModule authz_user_module     modules/mod_authz_user.so
LoadModule autoindex_module      modules/mod_autoindex.so
LoadModule dir_module            modules/mod_dir.so
LoadModule env_module            modules/mod_env.so
LoadModule headers_module        modules/mod_headers.so
LoadModule include_module        modules/mod_include.so
LoadModule isapi_module          modules/mod_isapi.so
LoadModule log_config_module     modules/mod_log_config.so
LoadModule mime_module           modules/mod_mime.so
LoadModule negotiation_module    modules/mod_negotiation.so
LoadModule setenvif_module       modules/mod_setenvif.so
LoadModule socache_shmcb_module  modules/mod_socache_shmcb.so
LoadModule ssl_module            modules/mod_ssl.so

 

Keep the list short. Anything you don’t need — remove. “Might come in handy” is a poor security strategy.

 

2. “Deny by default” policy at the root

 

Lock everything down, then open only the required directories selectively.

 

Unobtrusive directories/files (backups, temporary ones, forgotten dev folders) can suddenly become anonymously accessible.

 

Default-deny > Default-allow. It’s easier to mistakenly add an extra Require all granted than to remember to close something that shouldn’t be exposed.

 

<Directory />
    AllowOverride none
    Require all denied
</Directory>

 

First forbid everything, then grant exactly as much access as the site needs.

 

3. Harden the site directory: indexes, .ht*, and managed access

 

Disable auto-indexing, “shield” .ht*, and don’t let .htaccess spoil your policy.

 

  • Options Indexes → leakage of project structure and static files.
  • Allowed .htaccess → local RCE/rule bypass via a single committed file.
  • Hidden/service files → direct reading of secrets.

 

.htaccess is convenient but unsafe: scattered policy, slower I/O, higher risk of config injection if the deployment is compromised.

 

<Directory "${SRVROOT}/htdocs">
    Options -Indexes +FollowSymLinks
    AllowOverride None
    Require all granted
    <FilesMatch "^\.(?!well-known/).+">
        Require all denied
    </FilesMatch>
    <FilesMatch "\.(env|ini|cfg|conf|bak|swp)$">
        Require all denied
    </FilesMatch>
</Directory>
<Files ".ht*">
    Require all denied
</Files>

 

Close indexes, forbid .htaccess and “invisible” files, and leave only what users actually need.

 

4. Complete ban on CGI

 

CGI is a classic source of vulnerabilities: executing external interpreters via an HTTP request.

 

A buggy CGI script == RCE. Even if you don’t use CGI, a directory left “just in case” is a RISK.

 

The “disable interpreters on the web” principle minimizes the threat model.

 

<Directory "${SRVROOT}/cgi-bin">
    AllowOverride None
    Options None
    Require all denied
</Directory>
<LocationMatch "^/cgi-bin/?">
    Require all denied
</LocationMatch>

 

No CGI — no class of attacks that rely on it.

 

5. Minimizing metadata leaks and tracing vectors

 

Hide version info, disable TRACE, don’t give scanners hints.

 

Simple banner-grabbing matches an exploit to your version. TRACE helps with XST and header tricks.

 

Security by configuration: don’t give attackers extra telemetry; remove rarely needed methods.

 

TraceEnable off
ServerTokens Prod
ServerSignature Off
HostnameLookups Off

 

The less a scanner knows about you, the harder it is to aim.

 

6. Strict security headers (XFO, XCTO, XSSP, Referrer, Permissions)

 

Restrict framing, MIME sniffing, referrers, and access to devices.

 

  • Clickjacking via <iframe> without X-Frame-Options.
  • MIME sniffing → uploading JS as “text” turns into executable script.
  • Excess referrers → leakage of tokens/URLs with secrets.
  • Permissions-Policy → limit potential API abuse.

 

Frontend security is browser policy. Headers are your compact CSP-lite.

 

<IfModule headers_module>
    RequestHeader unset Proxy early
    Header always set X-Frame-Options "SAMEORIGIN"
    Header always set X-Content-Type-Options "nosniff"
    Header always set X-XSS-Protection "1; mode=block"
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
    Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
</IfModule>

 

Add a full CSP in production. X-XSS-Protection is deprecated but harmless — the real value comes from CSP.

 

7. Blocking encoded path bypasses

 

Forbid decoding of encoded slashes and catch traversal/exploit attempts via suspicious URIs.

 

%2f, %5c, double/triple encoding and “..” can punch through route checks and pull files outside DocumentRoot or land in an unexpected location.

 

URI normalization is painful. It’s simpler to explicitly cut off non-standard forms and double encodings.

 

AllowEncodedSlashes NoDecode
SetEnvIfNoCase Request_URI "(?i)(?:/cgi-bin\b|/bin/sh\b)" BAD_URI=1
SetEnvIfNoCase Request_URI "(?i)(?:\.\.|%2e%2e|%25%32%65%25%32%65|%252e%252e|%2f%2e%2e|%5c%2e%2e)" BAD_URI=1
SetEnvIfNoCase Request_URI "(?i)(?:%2f|%5c)" BAD_URI=1
<Location "/">
  <RequireAll>
    Require all granted
    Require not env BAD_URI
  </RequireAll>
  <LimitExcept GET POST HEAD>
    Require all denied
  </LimitExcept>
</Location>

 

This is a lightweight “WAF-lite”. It doesn’t replace a full WAF, but it will cut a lot of junk and dangerous requests.

 

8. Whitelist of methods

 

Limit methods to the safe minimum (reads/simple forms).

 

Methods like PUT, DELETE, OPTIONS, PROPFIND, and exotics can “light up” through a module/app bug and grant write/delete.

 

Least functionality: enable only what routes truly need.

 

<LimitExcept GET POST HEAD>
    Require all denied
</LimitExcept>

 

If the app needs more, you know where to open it — selectively, not “everywhere”.

 

9. Limiting request sizes (anti-DoS/anti-smuggling)

 

Protect the header parser and upstream from huge/dirty requests.

 

Large headers/many fields are classic low-rate DoS. Also fewer chances for clever overflow/offset attacks (HTTP Request Smuggling loves non-standard headers).

 

Rate-limiting != size-limiting. You need both. This is the size limiter.

 

LimitRequestLine       8190
LimitRequestFieldSize  8190
LimitRequestFields     100

 

Values are reasonable for most frameworks. If you have unusually large JWTs in cookies, reconsider the design — not the boundaries.

 

10. TLS: sessions and OCSP stapling

 

More stable and faster TLS sessions and certificate revocation checking (stapling).

 

Technically harder to “break in,” but without stapling → clients query the OCSP server themselves (leaks, instability). Without a session cache, it’s easier to choke the CPU with spikes of TLS handshakes.

 

OCSP stapling — the server attaches the certificate’s fresh status to the handshake; caches — reduce the cost of repeat connections.

 

<IfModule ssl_module>
    SSLRandomSeed startup builtin
    SSLRandomSeed connect builtin
    SSLSessionCache        "shmcb:${SRVROOT}/logs/ssl_scache(512000)"
    SSLSessionCacheTimeout 300
    SSLUseStapling On
    SSLStaplingCache "shmcb:${SRVROOT}/logs/stapling_cache(128000)"
</IfModule>

 

This is the basic hygiene minimum for TLS.

 

11. Enforcing HTTPS

 

Any HTTP request → redirect to HTTPS. Eliminate mixed traffic.

 

MitM, cookie interception, content tampering, downgrade attacks. HTTP is a postcard without an envelope.

 

HTTPS-everywhere + HSTS is the industry standard. First the redirect, then HSTS (with preload — as appropriate).

 

<VirtualHost *:80>
    ServerName  example.com
    ServerAlias www.example.com
    Redirect permanent / https://example.com/
    ErrorLog  "logs/redirect_error.log"
    CustomLog "logs/redirect_access.log" combined
</VirtualHost>

 

We’ll dive deeper into this later.

 

12. Logs as a security tool (detection and incident analysis)

 

Without logs you’re blind: you won’t see scans, brute-force attempts, LFI/RCE, or unexpected methods.

 

The attacker “fades into the shadows”: without correct format and separate logs, the investigation will drag on or become impossible.

 

Observability is part of security. Access + error logs, separate files for redirects/SSL/application.

 

ErrorLog "logs/error.log"
LogLevel warn
<IfModule log_config_module>
    LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
    LogFormat "%h %l %u %t \"%r\" %>s %b" common
    <IfModule logio_module>
        LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio
    </IfModule>
    CustomLog "logs/access.log" combined
</IfModule>

 

Personally, I periodically feed the logs to AI for checks. Yes, a crutch — I’ll automate it in the future.

 

13. Anti-proxy junk in requests

 

Strip the suspicious Proxy header early — sometimes helpful against crooked proxy chains/bugs.

 

Vulnerabilities at proxy/backend boundaries often flare up from unexpected service headers in raw form.

 

Header hygiene: anything the application doesn’t need is better cleaned/reserved.

 

RequestHeader unset Proxy early

 

A small thing, but nice. Especially in a zoo of CDNs, layered proxies, and backends.


Summary
 

Here’s roughly the code you should end up with:

 

Define SRVROOT "C:/Apache24"
ServerRoot "${SRVROOT}"
Listen 80
LoadModule access_compat_module modules/mod_access_compat.so
LoadModule actions_module        modules/mod_actions.so
LoadModule alias_module          modules/mod_alias.so
LoadModule allowmethods_module   modules/mod_allowmethods.so
LoadModule auth_basic_module     modules/mod_auth_basic.so
LoadModule authn_core_module     modules/mod_authn_core.so
LoadModule authn_file_module     modules/mod_authn_file.so
LoadModule authz_core_module     modules/mod_authz_core.so
LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
LoadModule authz_host_module     modules/mod_authz_host.so
LoadModule authz_user_module     modules/mod_authz_user.so
LoadModule autoindex_module      modules/mod_autoindex.so
LoadModule dir_module            modules/mod_dir.so
LoadModule env_module            modules/mod_env.so
LoadModule headers_module        modules/mod_headers.so
LoadModule include_module        modules/mod_include.so
LoadModule isapi_module          modules/mod_isapi.so
LoadModule log_config_module     modules/mod_log_config.so
LoadModule mime_module           modules/mod_mime.so
LoadModule negotiation_module    modules/mod_negotiation.so
LoadModule setenvif_module       modules/mod_setenvif.so
LoadModule socache_shmcb_module  modules/mod_socache_shmcb.so
LoadModule ssl_module            modules/mod_ssl.so
LoadFile  "C:/Program Files/Python313/python313.dll"
LoadModule wsgi_module "C:/Program Files/Python313/Lib/site-packages/mod_wsgi/server/mod_wsgi.cp313-win_amd64.pyd"
WSGIPythonHome "C:/Apache24/htdocs/venv"
WSGIPythonPath "C:/Apache24/htdocs/our_project"
<IfModule unixd_module>
   User daemon
   Group daemon
</IfModule>
ServerAdmin admin@example.com
ServerName  example.com
<Directory />
   AllowOverride none
   Require all denied
</Directory>
DocumentRoot "${SRVROOT}/htdocs"
<Directory "${SRVROOT}/htdocs">
   Options -Indexes +FollowSymLinks
   AllowOverride None
   Require all granted
   <FilesMatch "^\.(?!well-known/).+">
       Require all denied
   </FilesMatch>
   <FilesMatch "\.(env|ini|cfg|conf|bak|swp)$">
       Require all denied
   </FilesMatch>
</Directory>
<IfModule dir_module>
   DirectoryIndex index.html
</IfModule>
<Files ".ht*">
   Require all denied
</Files>
ErrorLog "logs/error.log"
LogLevel warn
<IfModule log_config_module>
   LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
   LogFormat "%h %l %u %t \"%r\" %>s %b" common
   <IfModule logio_module>
       LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio
   </IfModule>
   CustomLog "logs/access.log" combined
</IfModule>
<Directory "${SRVROOT}/cgi-bin">
   AllowOverride None
   Options None
   Require all denied
</Directory>
<LocationMatch "^/cgi-bin/?">
   Require all denied
</LocationMatch>
<IfModule mime_module>
   TypesConfig conf/mime.types
   AddType application/x-compress .Z
   AddType application/x-gzip     .gz .tgz
</IfModule>
<IfModule proxy_html_module>
   Include conf/extra/proxy-html.conf
</IfModule>
TraceEnable off
ServerTokens Prod
ServerSignature Off
HostnameLookups Off
<IfModule headers_module>
   RequestHeader unset Proxy early
   Header always set X-Frame-Options "SAMEORIGIN"
   Header always set X-Content-Type-Options "nosniff"
   Header always set X-XSS-Protection "1; mode=block"
   Header always set Referrer-Policy "strict-origin-when-cross-origin"
   Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
</IfModule>
AllowEncodedSlashes NoDecode
SetEnvIfNoCase Request_URI "(?i)(?:/cgi-bin\b|/bin/sh\b)" BAD_URI=1
SetEnvIfNoCase Request_URI "(?i)(?:\.\.|%2e%2e|%25%32%65%25%32%65|%252e%252e|%2f%2e%2e|%5c%2e%2e)" BAD_URI=1
SetEnvIfNoCase Request_URI "(?i)(?:%2f|%5c)" BAD_URI=1
<Location "/">
 <RequireAll>
   Require all granted
   Require not env BAD_URI
 </RequireAll>
 <LimitExcept GET POST HEAD>
   Require all denied
 </LimitExcept>
</Location>
LimitRequestLine       8190
LimitRequestFieldSize  8190
LimitRequestFields     100
<IfModule ssl_module>
   SSLRandomSeed startup builtin
   SSLRandomSeed connect builtin
   SSLSessionCache        "shmcb:${SRVROOT}/logs/ssl_scache(512000)"
   SSLSessionCacheTimeout 300
   SSLUseStapling On
   SSLStaplingCache "shmcb:${SRVROOT}/logs/stapling_cache(128000)"
</IfModule>
<VirtualHost *:80>
   ServerName  example.com
   ServerAlias www.example.com
   Redirect permanent / https://example.com/
   ErrorLog  "logs/redirect_error.log"
   CustomLog "logs/redirect_access.log" combined
</VirtualHost>
Include conf/extra/httpd-ssl.conf