A missing blog post image

Oops, I did it again ! I should have said “httpd” of course, the famous Apache web server.


I was so bored of starting from scratch for each new VHOST created, so the idea of writing an article keeping a template with the best security measures (taken from many places, see Sources below) has come up.
And here we are, a brand new Markdown post about it ! :tada:

Let’s start with the first part !

Part 1 - Securing Apache

If you have never secured Apache globally, you should start with this part (although, if you do, skip it and go to the second part) !

In a few words, the configuration below will… :

  • Block all requests (your VHOSTs will have to allow them explicitly on their side)

  • Turn off Apache verbosity

  • Block all requests to a hidden resource (handful for .git/ folder for instance)

  • Enforce OWASP headers recommendations

  • Authorize Cookies only on secured connections

  • Reduce the default timeout for sessions and limit requests size to 1 MB (you can increase it if you need to)

  • Force clients to negotiate only with secure ciphers, on TLS v1.2 (see Sources)

Just create this file :

# nano /etc/apache2/conf.d/security.local.conf

… and paste the following :

<Directory />
    AllowOverride None
    Require all denied

ServerTokens Prod
ServerSignature Off
TraceEnable Off
FileETag None

RedirectMatch 404 "^.*\/\.(?!well-known).*$"

Header always set Referrer-Policy: "same-origin"
Header always set X-Content-Type-Options: "nosniff"
Header always set X-XSS-Protection: "0"
Header always set X-Frame-Options: "SAMEORIGIN"
Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; connect-src 'self'; img-src 'self'; style-src 'self'; frame-ancestors 'self'"

Header always edit Set-Cookie "^(.*)$" "$1;HttpOnly;Secure"

TimeOut 60
LimitRequestBody 1024000

SSLHonorCipherOrder On
SSLProtocol -ALL +TLSv1.2
SSLUseStapling On
SSLStaplingCache "shmcb:logs/stapling-cache(150000)"
# For Apache >= 2.4.3
SSLCompression Off
# For Apache >= 2.4.11
SSLSessionTickets Off

BrowserMatch "MSIE [2-6]" nokeepalive ssl-unclean-shutdown downgrade-1.0 force-response-1.0
BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown

You’ll need some new modules to enforce these measures (and those during the second part) :

# a2enmod headers rewrite ssl

I’d advise you to catch a glimpse of the modules already loaded with :
# apachectl -M
You can easily remove some of them you won’t use, as below :
# a2dismod cgi autoindex <...>

If you need any protection from DDoS or BruteForce attacks, I advise you to follow this tutorial.

Reload Apache to parse and apply the new configuration :

# systemctl reload apache2.service

Now, you’re all set for the second part :wink:

Part 2 - VHOST specific configuration

Below, you’ll find two configurations :

  1. The first one is supposed to be named 000-default.conf : It will listen to the port 80, and redirect to each HTTPS VHOST in function of the domain name requested (just complete it with as many RewriteCond as you need)

  2. The second one is the content you’ll have to duplicate for each of your VHOST. They will listen to the port 443 (let’s say with Let’s Encrypt support).

<VirtualHost *:80>

    ServerName your.domain.name
    ServerAlias www.your.domain.name
    ServerAdmin webmaster@your.domain.name

    LogLevel warn
    ErrorLog ${APACHE_LOG_DIR}/000-default_error.log
    CustomLog ${APACHE_LOG_DIR}/000-default_access.log combined

    RewriteEngine On
    RewriteCond %{HTTP_HOST} ^your.domain.name$ [OR]
    RewriteCond %{HTTP_HOST} ^www.your.domain.name$
    RewriteRule ^(.*)$ https://%{HTTP_HOST}$1 [R=301,L]


<IfModule mod_ssl.c>
<VirtualHost *:443>

    ServerName your.domain.name
    ServerAlias www.your.domain.name
    ServerAdmin webmaster@your.domain.name

    DocumentRoot /path/to/webroot/

    <Directory /path/to/webroot/>
        Options -Indexes -FollowSymLinks -Includes -ExecCGI
        AllowOverride None
        Require all granted

    Header always set Strict-Transport-Security: "max-age=63072000; includeSubDomains; preload"

    RewriteEngine On
    RewriteCond %{THE_REQUEST} !HTTP/1\.1$
    RewriteRule .* - [F]

    SSLEngine On
    SSLCertificateFile /etc/letsencrypt/live/your.domain.name/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/your.domain.name/privkey.pem

    LogLevel warn
    ErrorLog ${APACHE_LOG_DIR}/your.domain.name_error.log
    CustomLog ${APACHE_LOG_DIR}/your.domain.name_access.log combined


This VHOST needs some explanations :

  • Set the webroot and harden the website authorization

  • Set HSTS header (see Sources)

  • Disable (only) HTTP 1.0 requests

  • Loads Let’s Encrypt SSL certificate

    We should say “TLS”, shouldn’t we ? (Coucou Nico’ :wave:)

  • Set dedicated logs (useful to debug and monitor which website is under attack)

Don’t forget to enable each one of your websites with :
# a2ensite <website>


You may encounter a side effect of your VHOST naming.
Try to access your web server directly with its IP, instead of one of the VHOST’s domain names) : http://your.server.address.ip/.
It’s very likely that you don’t want this website to answer when crawlers will start dealing with your server.
To recover from this behavior, just rename the VHOST you want to answer by default to something like 001-your-domain-name.conf.
When your IP will be accessed on the port 443, Apache will just redirect the query to the first VHOST name in a “top-down” way.

Last words

If you want to use a development Framework, it will surely shipped with a .htaccess file. Don’t forget to pass the AllowOverride to All to authorize your default settings to be overridden for a specific VHOST.
On the same idea, some of them (hello CakePHP :wave:) use symbolic link to enable their plugins. So, you may need to activate FollowSymLinks too…

It’s good to secure your web server, but if you are hosting a PHP application for instance, it won’t block any attack occurring at the “application level”…

So this is the end, I hope it helped you :+1:

Please do propose some changes if you think there is something bad (or something missing) ; I’ll try to keep this article up-to-date in the future ! :ok_hand:


History of revisions

2022-09-04 - Drop support for Apache < 2.4 and replace _default_ by *
2022-01-27 - Disable legacy XSS filtering through X-XSS-Protection header
2020-03-16 - Due to this notice, cipherli.st has been replaced by safeciphers.org
2019-11-16 - always set/edit security headers
2018-11-24 - Grants access to special (not-hidden) /.well-known/ paths
2018-11-11 - Gets rid of CBC encryption mode in ciphers list
2018-10-12 - Adds a default Referrer-Policy header to the local security configuration
2018-08-01 - Replaces Order, Allow and Deny options by Require for Apache >= 2.4, in a BC way for previous versions
2018-01-14 - Fixes wrong HSTS header setting
2018-01-09 - Adds Content Security Policy header to security.local.conf
2017-11-21 - Adds SSLStapling and Internet Explorer specific directives