1. Switch to Encrypted Port
Listen only on 443 for secure traffic. This removes the temptation to keep functionality on unencrypted HTTP and reduces the attack surface for MitM.
How it’s broken without this: traffic interception and substitution, cookie/session theft, downgrade to HTTP.
Listen 443
Basic hygiene — all sensitive data only over TLS.
2. Isolated Default Virtual Host
_default_:443 ensures that any host hitting 443 without an explicit VirtualHost lands in a safe profile.
<VirtualHost _default_:443>
ServerAdmin admin@example.com
ServerName example.com
DocumentRoot "C:/Apache24/htdocs"
#</VirtualHost> will be at the end
3. Enable TLS and Correct Certificates
Enable TLS and specify the chain/key. Correct files and permissions are the basis of trust.
How it’s broken without this: self-signed/broken chains → phishing via warnings; private key leak → total compromise.
SSLEngine on
SSLCertificateFile "C:/Apache24/conf/ssl/domain.cert.pem"
SSLCertificateKeyFile "C:/Apache24/conf/ssl/private.key.pem"
Keep keys outside the web-root, readable only by the service user.
4. Protocol and Cipher Policy
Disable outdated protocols and weak ciphers, enforce server cipher order.
How it’s broken without this: downgrade to TLS 1.0/1.1, attacks on CBC/3DES/RC4/MD5, BEAST/Lucky13, etc.
SSLProtocol -all +TLSv1.2 +TLSv1.3
SSLCipherSuite HIGH:!aNULL:!MD5:!3DES
SSLHonorCipherOrder On
SSLCompression Off
Bottom line: Minimum — TLS 1.2/1.3, disable compression (CRIME) and trash ciphers.
5. Certificate Status Check (OCSP stapling)
The server attaches a fresh certificate status. Clients don’t need to contact the OCSP server.
Without this, privacy is worse (leaked OCSP queries), unstable checks ⇒ users ignore warnings.
SSLUseStapling On
Stapling improves both privacy and performance.
6. Enforce HSTS
Force browsers to use only HTTPS, including subdomains, and allow preload.
How it’s broken without this: downgrade to HTTP, cookie interception, mixed content.
<IfModule headers_module>
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
</IfModule>
Note: enable HSTS only after the entire domain is really ready to live on HTTPS.
7. WSGI Entry Point with Minimal Rights
Explicitly map the root to the WSGI app and forbid directory listing.
How it’s broken without this: random static/service files exposed, routing bypass to files.
WSGIScriptAlias / "C:/Apache24/htdocs/our_project/our_project/wsgi.py"
<Directory "C:/Apache24/htdocs/our_project/our_project/">
<Files wsgi.py>
Require all granted
Options -Indexes
</Files>
</Directory>
8. Static Files: Open but No Indexes
Serve static files without auto-listing to avoid exposing project structure.
Alias /static "C:/Apache24/htdocs/our_project/staticfiles/"
<Directory "C:/Apache24/htdocs/our_project/staticfiles/">
Require all granted
Options -Indexes
</Directory>
Result: only what’s explicitly addressed is visible.
9. Media: Block Dangerous Extensions + Cache
Serve user files but block anything executable.
How it’s broken without this: upload and execution of .py/.exe/.ps1, etc. → RCE/privilege escalation, payload hosting.
Alias /media/ "C:/Apache24/htdocs/our_project/media/"
<Directory "C:/Apache24/htdocs/our_project/media/">
Require all granted
Options -Indexes
<FilesMatch "\.(py|pyc|pyo|sh|bat|cmd|ps1|exe)$">
Require all denied
</FilesMatch>
<IfModule mod_expires.c>
ExpiresActive On
ExpiresDefault "access plus 7 days"
</IfModule>
</Directory>
Media are data, not code. Deny executable formats and set a safe TTL.
10. Limit Request Body Size
Set a limit to protect the backend and prevent abuse of uploads.
Prevents DoS with huge bodies, massive archives/dictionary attacks via upload.
LimitRequestBody 26214400 # 25 MiB
Adjust the limit to real needs; better smaller than “maybe later.”
11. Separate Logs for TLS Virtual Host
Separate error/access logs simplify incident investigation over HTTPS.
How it’s broken without this: noisy shared rotation hides anomalies, context lost.
ErrorLog "logs/ssl_error.log"
CustomLog "logs/ssl_access.log" combined
</VirtualHost>
Personally I feed logs to AI for analysis. As in the previous article, I admit it’s a crutch. But apparently until I get hacked, I won’t automate.
Summary
+- Here’s how it should look:
Listen 443
ServerAdmin admin@example.com
ServerName example.com
DocumentRoot "C:/Apache24/htdocs"
SSLEngine on
SSLCertificateFile "C:/Apache24/conf/ssl/domain.cert.pem"
SSLCertificateKeyFile "C:/Apache24/conf/ssl/private.key.pem"
SSLProtocol -all +TLSv1.2 +TLSv1.3
SSLCipherSuite HIGH:!aNULL:!MD5:!3DES
SSLHonorCipherOrder On
SSLCompression Off
SSLUseStapling On
<IfModule headers_module>
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
</IfModule>
WSGIScriptAlias / "C:/Apache24/htdocs/our_project/our_project/wsgi.py"
<Directory "C:/Apache24/htdocs/our_project/our_project/">
<Files wsgi.py>
Require all granted
Options -Indexes
</Files>
</Directory>
Alias /static "C:/Apache24/htdocs/our_project/staticfiles/"
<Directory "C:/Apache24/htdocs/our_project/staticfiles/">
Require all granted
Options -Indexes
</Directory>
Alias /media/ "C:/Apache24/htdocs/our_project/media/"
<Directory "C:/Apache24/htdocs/our_project/media/">
Require all granted
Options -Indexes
<FilesMatch "\.(py|pyc|pyo|sh|bat|cmd|ps1|exe)$">
Require all denied
</FilesMatch>
<IfModule mod_expires.c>
ExpiresActive On
ExpiresDefault "access plus 7 days"
</IfModule>
</Directory>
LimitRequestBody 26214400
ErrorLog "logs/ssl_error.log"
CustomLog "logs/ssl_access.log" combined