---
title: "Serveur Web — Configuration & Durcissement"
domain: network
subdomain: services
type: snippet
tags: [apache, nginx, tls, https, hardening, headers, reverse-proxy, csp, hsts, ssl]
difficulty: intermediate
status: stable
updated: "2026-05-14"
---
## Apache

### Installation et structure

```bash
apt install apache2
a2enmod ssl rewrite headers http2

# Structure
/etc/apache2/apache2.conf       # config principale
/etc/apache2/sites-available/   # vhosts disponibles
/etc/apache2/sites-enabled/     # vhosts actifs (symlinks)
/etc/apache2/conf-available/    # configs supplémentaires
```

### Masquer la version et le banner

```apache
# /etc/apache2/conf-available/security.conf
ServerTokens Prod          # affiche "Apache" uniquement, sans version
ServerSignature Off        # supprime la signature dans les pages d'erreur
TraceEnable Off            # désactive la méthode HTTP TRACE
```

### Virtual Host HTTPS

```apache
# /etc/apache2/sites-available/example.com.conf
<VirtualHost *:80>
    ServerName example.com
    Redirect permanent / https://example.com/
</VirtualHost>

<VirtualHost *:443>
    ServerName example.com
    DocumentRoot /var/www/example.com

    SSLEngine on
    SSLCertificateFile    /etc/ssl/certs/example.com.crt
    SSLCertificateKeyFile /etc/ssl/private/example.com.key
    SSLCertificateChainFile /etc/ssl/certs/ca-bundle.crt

    # TLS — désactiver les protocoles obsolètes
    SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
    SSLCipherSuite ECDH+AESGCM:ECDH+CHACHA20:DH+AESGCM:!aNULL:!MD5:!RC4
    SSLHonorCipherOrder on
    SSLCompression off
    SSLSessionTickets off

    # Headers de sécurité
    Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
    Header always set X-Frame-Options "DENY"
    Header always set X-Content-Type-Options "nosniff"
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
    Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
    Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:"

    # Supprimer les headers qui exposent la stack
    Header unset X-Powered-By
    Header unset Server

    # Interdire le listing de répertoires
    <Directory /var/www/example.com>
        Options -Indexes -FollowSymLinks
        AllowOverride None
        Require all granted
    </Directory>

    # Logs
    ErrorLog  ${APACHE_LOG_DIR}/example.com_error.log
    CustomLog ${APACHE_LOG_DIR}/example.com_access.log combined
</VirtualHost>
```

### Reverse Proxy Apache

```apache
a2enmod proxy proxy_http proxy_balancer lbmethod_byrequests

<VirtualHost *:443>
    ServerName app.example.com

    SSLEngine on
    # ... (cert TLS comme ci-dessus)

    # Proxy vers le backend
    ProxyPreserveHost On
    ProxyPass        / http://127.0.0.1:8080/
    ProxyPassReverse / http://127.0.0.1:8080/

    # Timeout backend
    ProxyTimeout 60

    # Masquer le backend dans les réponses
    Header unset X-Powered-By
</VirtualHost>
```

### Limiter les méthodes HTTP

```apache
<Directory /var/www/example.com>
    <LimitExcept GET POST HEAD>
        Require all denied
    </LimitExcept>
</Directory>
```

### Commandes de gestion

```bash
# Vérifier la configuration
apachectl configtest

# Activer/désactiver un vhost
a2ensite example.com.conf
a2dissite example.com.conf
systemctl reload apache2

# Vérifier les modules actifs
apache2ctl -M | sort

# Voir les vhosts configurés
apache2ctl -S
```

---

## Nginx

### Configuration globale — `/etc/nginx/nginx.conf`

```nginx
user www-data;
worker_processes auto;
pid /run/nginx.pid;

events {
    worker_connections 1024;
    multi_accept on;
}

http {
    # Masquer la version Nginx
    server_tokens off;

    # Timeouts (protection contre slowloris)
    client_body_timeout   12;
    client_header_timeout 12;
    keepalive_timeout     15;
    send_timeout          10;

    # Taille max des requêtes
    client_max_body_size 10M;

    # Logs
    access_log /var/log/nginx/access.log;
    error_log  /var/log/nginx/error.log warn;

    include /etc/nginx/mime.types;
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}
```

### Virtual Host HTTPS — `/etc/nginx/sites-available/example.com`

```nginx
# Redirection HTTP → HTTPS
server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name example.com www.example.com;

    root /var/www/example.com;
    index index.html;

    # TLS
    ssl_certificate     /etc/ssl/certs/example.com.crt;
    ssl_certificate_key /etc/ssl/private/example.com.key;
    ssl_dhparam         /etc/ssl/certs/dhparam.pem;       # openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDH+AESGCM:ECDH+CHACHA20:DH+AESGCM:!aNULL:!MD5:!RC4;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;

    # OCSP Stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 1.1.1.1 8.8.8.8 valid=300s;

    # Headers de sécurité
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
    add_header X-Frame-Options "DENY" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
    add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline';" always;

    # Interdire le listing de répertoires
    autoindex off;

    # Bloquer les méthodes non nécessaires
    if ($request_method !~ ^(GET|POST|HEAD)$) {
        return 405;
    }

    location / {
        try_files $uri $uri/ =404;
    }

    # Bloquer l'accès aux fichiers sensibles
    location ~ /\. {
        deny all;
    }

    location ~* \.(env|log|conf|bak|sql|git)$ {
        deny all;
    }
}
```

### Reverse Proxy Nginx

```nginx
upstream backend {
    server 127.0.0.1:8080;
    server 127.0.0.1:8081 backup;
    keepalive 32;
}

server {
    listen 443 ssl http2;
    server_name app.example.com;

    # ... (TLS et headers comme ci-dessus)

    location / {
        proxy_pass http://backend;
        proxy_http_version 1.1;
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade           $http_upgrade;
        proxy_set_header Connection        "upgrade";
        proxy_read_timeout 60s;

        # Masquer les headers du backend
        proxy_hide_header X-Powered-By;
        proxy_hide_header Server;
    }
}
```

### Commandes de gestion

```bash
# Générer le paramètre Diffie-Hellman
openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048

# Vérifier la configuration
nginx -t

# Recharger sans interruption
systemctl reload nginx

# Voir les vhosts actifs
nginx -T | grep server_name

# Activer un vhost
ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
```

---

## Certificats SSL/TLS

```bash
# Let's Encrypt (Certbot) — Apache
apt install certbot python3-certbot-apache
certbot --apache -d example.com -d www.example.com

# Let's Encrypt — Nginx
apt install certbot python3-certbot-nginx
certbot --nginx -d example.com

# Renouvellement automatique (cron)
certbot renew --dry-run

# Certificat auto-signé (tests internes)
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout /etc/ssl/private/self-signed.key \
  -out /etc/ssl/certs/self-signed.crt \
  -subj "/CN=srv-web.example.local/O=Example/C=FR"
```

---

## Vérification et tests

```bash
# Headers HTTP retournés
curl -sI https://example.com

# Audit TLS complet
testssl.sh https://example.com
# Points à valider : TLS 1.0/1.1 désactivés, Forward Secrecy, HSTS

# Ciphers disponibles
nmap --script ssl-enum-ciphers -p 443 example.com

# Méthodes HTTP autorisées
curl -sI -X OPTIONS https://example.com | grep Allow
curl -sI -X TRACE https://example.com   # doit retourner 405

# Listing de répertoires
curl -s https://example.com/images/ | grep -i "Index of"

# Vérifier le banner serveur (doit être absent ou générique)
curl -sI https://example.com | grep -iE "^Server:|^X-Powered-By:"
```

<Checklist
  storageKey="net-webserver-config"
  items={[
    "HTTP redirigé vers HTTPS (301 permanent)",
    "TLS 1.2+ uniquement — TLS 1.0/1.1 et SSLv3 désactivés",
    "Ciphers forts uniquement (ECDHE, AES-GCM, ChaCha20)",
    "HSTS configuré (max-age ≥ 63072000 + includeSubDomains)",
    "X-Frame-Options: DENY configuré",
    "X-Content-Type-Options: nosniff configuré",
    "Content-Security-Policy définie",
    "Referrer-Policy configurée",
    "ServerTokens Prod / server_tokens off (version masquée)",
    "Méthodes HTTP restreintes (GET, POST, HEAD uniquement)",
    "Listing de répertoires désactivé (Options -Indexes / autoindex off)",
    "Accès aux fichiers sensibles (.env, .git, .log) bloqué",
    "Logs d'accès et d'erreur actifs",
    "Certificat valide, non expiré, renouvellement automatique"
  ]}
/>

<Tip>
Générer le paramètre Diffie-Hellman personnalisé (`openssl dhparam 2048`) avant de déployer HTTPS — sans ça, Nginx utilise des paramètres DH par défaut parfois faibles selon la version.
</Tip>
