Il existe bien des manières de configurer son serveur web sans qu’une soit nécessairement meilleure que les autres. Mais parce que l’on me demande régulièrement des conseils sur ce sujet et que je veux bien aider mais pas me répéter, j’explique ici ma manière de voir les choses. Copiez, modifiez, inspirez-vous, critiquez, c’est open-bar.
Arborescence des fichiers #
/etc/nginx
├── custom
│ ├── anti-bots.conf
│ ├── force-download.conf
│ ├── headers.conf
│ ├── headers-nocsp.conf
│ ├── php.conf
│ ├── tls.conf
│ └── tls-headers.conf
├── fastcgi.conf
├── fastcgi_params
├── koi-utf
├── koi-win
├── mime.types
├── modules.d
├── nginx.conf
├── scgi_params
├── uwsgi_params
├── websites
│ ├── auranet.conf
│ ├── blog.conf
│ ├── default.conf
│ ├── freebox.conf
│ └── projects.conf
└── win-utf
À la racine, on retrouve tous les fichiers par défaut fournis avec
l’installation du paquet. Je vous laisse lire la documentation de votre
distribution pour savoir comment les utiliser. Il n’y a véritablement que le
fichier principal, nginx.conf
, que j’ai modifié comme suit :
user http;
worker_processes auto;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
server_tokens off;
port_in_redirect off;
types_hash_bucket_size 128;
charset utf-8;
index index.html;
sendfile on;
keepalive_timeout 65;
gzip on;
access_log off;
include websites/default.conf;
include websites/example.com.conf;
}
Pour user
, laissez donc la valeur indiquée de base qui sera en accord avec
d’éventuels changements dans la conception du paquet de votre distribution.
Ce qu’il est intéressant de voir ici, c’est que j’ai mis l’intégralité des
blocs server
dans des fichiers séparés par domaines et localisés dans le
répertoire websites
. Bien entendu, default.conf
correspond à ce qui sera
servi par défaut si une personne demande un domaine non existant sur l’IPv4.
Pour l’IPv6, on utilisera une adresse différente par serveur afin de ne pas
avoir ce genre de problèmes.
server {
listen 198.51.100.42:80 default_server;
server_name "";
include custom/headers.conf;
return 403;
}
server {
listen 198.51.100.42:443 default_server ssl;
http2 on;
server_name "";
include custom/headers.conf;
include custom/tls.conf;
include custom/anti-bots.conf;
ssl_certificate /var/lib/acmed/certs/exemple.com_ecdsa-p384.crt.pem;
ssl_certificate_key /var/lib/acmed/certs/exemple.com_ecdsa-p384.pk.pem;
return 403;
}
Chaque véritable application web est configurée dans un fichier au nom du domaine sur laquelle elle se trouve :
server {
listen 198.51.100.42:80;
listen [2001:db8::42]:80;
server_name example.com;
include custom/headers.conf;
root /srv/http/example.com;
location ~ ^/(?!(\.well-known)) {
return 301 https://example.com$request_uri;
}
}
server {
listen 198.51.100.42:443 ssl;
listen [2001:db8::42]:443 quic reuseport;
listen [2001:db8::42]:443 ssl;
http2 on;
server_name example.com;
include custom/headers.conf;
include custom/tls.conf;
ssl_certificate /var/lib/acmed/certs/exemple.com_ecdsa-p384.crt.pem;
ssl_certificate_key /var/lib/acmed/certs/exemple.com_ecdsa-p384.pk.pem;
root /srv/http/example.com;
error_page 404 /404/index.html;
}
Ici, le premier bloc server
sert à rediriger les requêtes HTTP vers HTTPS, à
l’exception des requêtes dont l’URL commence par /.well-known
. Ces dernières
sont en effet utilisées pour des taches spéciales, en particulier pour répondre
aux défis HTTP-01 du protocole ACME. Les points relatifs au service lui-même se
trouvent dans le second bloc server
qui, lui, est réservé à HTTPS, d’où
l’écoute sur le port 443.
À noter que, bien qu’il ne soit théoriquement pas nécessaire d’utiliser TLS sur HTTP/2, c’est en réalité imposé par la plupart des implémentations, d’où le fait qu’on ne le retrouve pas sur le bloc réservé à HTTP.
Notez la présence d’un listen
en double sur l’adresse IPv6. Le premier
contient quic reuseport
, ce qui permet d’accepter HTTP/3. Si cela fonctionne
également en IPv4, il n’est en revanche possible de le spécifier qu’une seule
fois par adresse IP. Dans la mesure où la plupart du temps l’adresse IPv4 est
réutilisée alors que l’on utilise des adresses IPv6 uniques, j’ai ici préféré
ne le mettre que pour l’IPv6. Au besoin, pensez à adapter à votre situation.
Et maintenant, passons aux choses sérieuses…
Les fichiers de configuration personalisés #
Au nombre de 7, ils sont situés dans le répertoire custom
. C’est dans ces
fichiers que je mets des bouts de configuration réutilisables dans différentes
applications, il n’y a qu’à les inclure dans le bloc server
correspondant.
anti-bots.conf #
S’il y a une chose que je déteste voir sur mon serveur web, ce sont des robots qui cherchent des failles de sécurité de manière totalement automatisée. J’ai donc préparé un fichier un peu spécial qui leur sera servi. Pour cela, commençons par générer un très gros fichier remplis de zéros et compressons le avec gzip afin qu’il ne prenne qu’environ 300 Mo d’espace sur le disque.
dd if=/dev/zero bs=1M count=$((300*1024)) | gzip >/srv/http/example.com/anti_bots/big_bomb.gz
Et maintenant on créé le fichier anti-bots.conf
qui, si l’URL contient au
moins un des motifs définis, retourne le contenu de ce fichier. Notez la
présence de gzip_static afin de dire que le fichier est déjà compressé et
qu’il ne faut donc pas le recompresser. Ainsi, le fichier sera renvoyé tel quel
avec l’en-tête HTTP indiquant que le contenu est compressé, ce qui forcera la
bibliothèque HTTP du bot à le décompresser afin d’en lire le contenu.
# Environment and configuration
location ~* /(\.env|\.git|composer\.json|composer\.lock|docker\.env|docker\.yml|docker\.yaml|Docker\.env|Docker\.yml|Docker\.yaml|mysql\.env|mysql\.yml|mysql\.yaml|config\.bak\.php) {
gzip on;
gzip_static always;
alias /srv/http/example.com/anti_bots/big_bomb;
}
# WordPress
location ~* /(wp-login\.php|atomlib\.php|wordpress|wp|wp2|old-wp|wp-includes) {
gzip on;
gzip_static always;
alias /srv/http/example.com/anti_bots/big_bomb;
}
# phpMyAdmin
location ~* /(phpmyadmin|pma) {
gzip on;
gzip_static always;
alias /srv/http/example.com/anti_bots/big_bomb;
}
# Misc
location ~* /(alfacgiapi|apple-app-site-association|class\.api\.php|cloud\.php|cms|frontend_dev\.php|github\.php|wso112233\.php|xmrlpc\.php) {
gzip on;
gzip_static always;
alias /srv/http/example.com/anti_bots/big_bomb;
}
Bien entendu, pensez à adapter les motifs en fonction de votre situation. Ce serait dommage d’accidentellement vous servir votre propre bombe de compression parce que vous avez un phpMyAdmin.
force-download.conf #
Parfois vous ne souhaitez pas que le client ouvre un fichier dans son navigateur, vous souhaitez au contraire forcer le téléchargement du fichier en question. Voici la configuration qui permet de faire ça, il ne reste plus qu’à l’inclure là où c’est pertinent.
types {}
default_type application/octet-stream;
headers-nocsp.conf #
C’est ici que sont ajoutés toute une série d’en-têtes HTTP communes à chaque application.
# HTTP/3
add_header 'Alt-Svc' 'h3=":443"; ma=86400';
# https://developer.mozilla.org/fr/docs/Web/HTTP/Headers/X-Frame-Options
add_header 'X-Frame-Options' 'DENY';
# https://developer.mozilla.org/fr/docs/Web/HTTP/Headers/X-Content-Type-Options
add_header 'X-Content-Type-Options' 'nosniff';
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy
add_header 'Referrer-Policy' 'same-origin';
# https://xclacksoverhead.org/home/about
# http://www.gnuterrypratchett.com/
add_header 'X-Clacks-Overhead' 'GNU Terry Pratchett';
Les paramètres sont intentionnellement en « mode paranoïaque » afin d’éviter tout problème.
headers.conf #
Inclue le fichier précédent et ajoute une politique de sécurité de contenu
très stricte. C’est le fichier que j’utilise par défaut : si une application
nécessite une CSP plus souple j’utilise headers-nocsp.conf
afin de préciser
autre chose.
include custom/headers-nocsp.conf;
add_header 'Content-Security-Policy' "default-src 'self'";
php.conf #
Tout simplement ma configuration PHP, utilisant chez moi FastCGI pour interfacer Nginx avec PHP-FPM.
index index.php;
location ~ \.php$ {
try_files $uri $document_root$fastcgi_script_name =404;
fastcgi_pass unix:/run/php-fpm/php-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi.conf;
}
tls.conf #
Et enfin, le meilleur étant pour la fin, la configuration TLS.
# Recommandations de l'ANSSI
# https://www.ssi.gouv.fr/administration/guide/recommandations-de-securite-relatives-a-tls/
# ANSSI R3 : Privilégier TLS 1.3 et accepter TLS 1.2
# ANSSI R4 : Ne pas utiliser SSLv2, SSLv3, TLS 1.0 et TLS 1.1
ssl_protocols TLSv1.2 TLSv1.3;
# ANSSI R13 : Préférer l’ordre de suites du serveur
ssl_prefer_server_ciphers on;
# ANSSI R6 : Échanger les clés en assurant toujours la PFS
# ANSSI R7 : Échanger les clés avec l’algorithme ECDHE
# ANSSI R9 : Privilégier AES ou ChaCha20
# ANSSI R10 : Utiliser un mode de chiffrement intègre
# ANSSI R12 : Disposer de plusieurs suites cryptographiques
#
# openssl ciphers -v -stdname 'ECDH:!SHA1:!SHA256:!SHA384:!aRSA:!AESCCM8:!ARIA'
# man openssl-ciphers
ssl_ciphers ECDH:!SHA1:!SHA256:!SHA384:!aRSA:!AESCCM8:!ARIA;
# ANSSI R7 : Échanger les clés avec l’algorithme ECDHE
# openssl ecparam -list_curves
ssl_ecdh_curve X25519:prime256v1:secp384r1:secp521r1:brainpoolP256r1:brainpoolP384r1:brainpoolP512r1;
# ANSSI R20 : Limiter la durée de vie des tickets
ssl_session_tickets on;
ssl_session_timeout 2m;
ssl_session_cache shared:SSL:10m;
# ANSSI R23 : Ne pas transmettre de données 0-RTT
ssl_early_data off;
# /!\ ANSSI R35 : Préférer l’agrafage OCSP
# OCSP Stapling: https://en.wikipedia.org/wiki/OCSP_stapling
#
# Bien que l'ANSSI recommande d'activer l'OCSP Stapling, Let's Encrypt ne
# supporte plus cette fonctionnalité.
# https://letsencrypt.org/2024/12/05/ending-ocsp/
ssl_stapling off;
ssl_stapling_verify off;
ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
include custom/tls-headers.conf;
Il ne reste plus qu’à indiquer dans chaque bloc server
où se trouvent la clé
privée ainsi que le certificat. Pour ce dernier, on n’oubliera pas d’inclure
les certificats intermédiaires.
Avec tout ça, si vos logiciels sont à jour, vous devriez avoir un joli A+ sur les test de configuration les plus stricts. À cette fin, citons le Qualys SSL Server test, nmap et testssl.sh.
nmap --script ssl-enum-ciphers -p 443 example.org
D’autres éléments de votre configuration peuvent être testés avec le Mozilla observatory.
tls-headers.conf #
Ce fichier est inclut par le fichier tls.conf
, cependant, dans certains cas
de configuration, il ne sera pas possible d’ajouter de header à l’endroit où
l’on configure TLS et l’import ne fonctionnera donc pas. J’ai donc créé un
fichier séparé afin de pouvoir l’inclure là où c’est pertinent.
# HSTS
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security
# 31536000 sec = 365 days
add_header 'Strict-Transport-Security' 'max-age=31536000; includeSubDomains; preload';
L’exemple de la fin : gogs/gitea/forgejo #
Afin de montrer un exemple de configuration avec des CSP ajustées au plus près d’une application, je vous propose ma configuration pour gogs/gitea/forgejo :
server {
listen 198.51.100.42:443 ssl;
listen [2001:db8::69]:443 quic reuseport;
listen [2001:db8::69]:443 ssl;
http2 on;
server_name git.example.com;
error_log /var/log/nginx/git.example.com-error.log;
include custom/anti-bots.conf;
include custom/headers-nocsp.conf;
add_header 'Content-Security-Policy' "default-src 'self'; connect-src 'self'; font-src 'self' data:; form-action 'self'; img-src 'self' https: data:; manifest-src 'self' data:; object-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; worker-src 'self'";
include custom/tls.conf;
ssl_certificate /etc/acmed/certs/exemple.com_ecdsa-p384.crt.pem;
ssl_certificate_key /etc/acmed/certs/exemple.com_ecdsa-p384.pk.pem;
location / {
proxy_pass http://127.0.0.1:3000/;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Le résultat #
Si vous avez suivi ce billet et que vous avez également bien configuré votre DNS, alors vous devriez obtenir une excellente notation sur les différents outils de test. À titre d’information, voici les résultats pour ce blog à date de la dernière mise à jour du présent billet.
Qualys SSL Labs #
Mozilla HTTP Observatory #