Publié le 01/12/2017, dernière modification le 16/03/2021.
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.
/etc/nginx
├── custom
│ ├── headers.conf
│ ├── headers-nocsp.conf
│ ├── php.conf
│ └── tls.conf
├── fastcgi.conf
├── fastcgi_params
├── koi-utf
├── koi-win
├── mime.types
├── nginx.conf
├── scgi_params
├── uwsgi_params
├── websites
│ ├── default.conf
│ └── example.com.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;
charset utf-8;
index index.html;
sendfile on;
keepalive_timeout 70;
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 404;
}
server {
listen 198.51.100.42:443 default_server ssl;
server_name "";
include custom/headers.conf;
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;
return 404;
}
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 http2;
listen [2001:db8::42]:443 ssl http2;
server_name example.com;
include custom/headers.conf;
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;
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.
Commençons par les directives listen
: j'y ajoute http2
à la fin afin d'utiliser le protocole HTTP/2 si le client le supporte. Bien qu'il ne soit théoriquement pas absolument nécessaire d'utiliser TLS sur HTTP/2, c'est un fait imposé par la plupart des implémentations, d'où le fait qu'on ne le retrouve pas sur le bloc réservé à HTTP.
Et maintenant, passons aux choses sérieuses…
Au nombre de 4, 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.
C'est ici que sont ajoutés toute une série d'en-têtes HTTP communes à chaque application.
add_header 'X-Frame-Options' 'DENY';
add_header 'X-XSS-Protection' '1; mode=block';
add_header 'X-Content-Type-Options' 'nosniff';
add_header 'Referrer-Policy' 'same-origin';
add_header 'X-Clacks-Overhead' 'GNU Terry Pratchett';
Les paramètres sont intentionnellement en « mode paranoïaque » afin d'éviter tout problème.
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'";
Tout simplement ma configuration PHP, utilisant chez moi FastCGI pour interfacer Nginx avec PHP-FPM. C'est tout droit tiré du wiki Arch Linux en ajoutant le nom du fichier d'index.
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;
}
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 / R4
ssl_protocols TLSv1.2 TLSv1.3;
# ANSSI R13
ssl_prefer_server_ciphers on;
# ANSSI R6 / R9 / R10
ssl_ciphers HIGH:!eNULL:!LOW:!MEDIUM:!EXP:!RC4:!3DES:!MD5:!SHA1:!SHA256:!SHA384:!PSK:!kRSA:!SRP:-DH:+ECDH;
# ANSSI R7
ssl_ecdh_curve secp521r1:secp384r1:prime256v1:X25519:X448:brainpoolP512r1:brainpoolP384r1:brainpoolP256r1;
# ANSSI R20
ssl_session_timeout 2m;
ssl_session_cache shared:SSL:10m;
# ANSSI R35
# OCSP Stapling
# https://en.wikipedia.org/wiki/OCSP_stapling
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;
# ANSSI R23
ssl_early_data off;
# https://en.wikipedia.org/wiki/BREACH
# (Not ANSSI R19 since both OpenSSL and nginx disabled TLS compression after CRIME CVE-2012-4929)
gzip off;
# 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';
# Certificate Transparency
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Expect-CT
# 86400 sec = 1 day
add_header 'Expect-CT' 'max-age=86400, enforce';
add_header
: ajoute une en-tête HTTP activant HSTS pour une longue durée ;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 et CryptCheck. D'autres éléments de votre configuration peuvent être testés avec le Mozilla observatory.
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 :
server {
listen 198.51.100.42:443 http2 ssl;
listen [2001:db8::69]:443 http2 ssl;
server_name git.example.com;
error_log /var/log/nginx/git.example.com-error.log;
include custom/headers-nocsp.conf;
add_header 'Content-Security-Policy' "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; font-src 'self' data:";
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;
}
}