Aide-mémoire Apache .htaccess
| Bases et Prérequis |
| Objectif | Configuration / Commande |
| Activer .htaccess (httpd.conf / apache2.conf) |
<Directory /var/www/html> AllowOverride All :: activer .htaccess </Directory>
:: Recharger Apache après modification : systemctl reload apache2 apachectl graceful |
| Modules à activer (Debian/Ubuntu) |
a2enmod rewrite :: mod_rewrite (URL) a2enmod headers :: en-têtes HTTP a2enmod deflate :: compression gzip a2enmod expires :: cache navigateur a2enmod auth_basic :: authentification HTTP systemctl restart apache2 |
| Commentaires |
# Ceci est un commentaire .htaccess # Une ligne par directive |
| mod_rewrite — Réécriture d'URL |
| Activer le moteur de réécriture |
RewriteEngine On RewriteBase / :: chemin de base (si sous-dossier : /monapp/) |
| RewriteRule — Syntaxe |
RewriteRule <pattern> <substitution> [flags]
:: pattern : regex sur l'URL demandée (sans le /) :: substitution : URL cible, ou - pour ne rien changer :: flags : L=dernier, R=redirect, F=403, G=410, etc. |
| RewriteCond — Condition |
RewriteCond %{VARIABLE} valeur [flags]
:: Variables utiles : :: %{HTTP_HOST} :: nom d'hôte :: %{REQUEST_URI} :: chemin demandé :: %{QUERY_STRING} :: paramètres GET :: %{REQUEST_METHOD}:: GET, POST, etc. :: %{HTTPS} :: on si HTTPS :: %{SERVER_PORT} :: 80, 443 :: %{REMOTE_ADDR} :: IP du visiteur :: %{HTTP_USER_AGENT}:: navigateur client |
| Réécriture simple — URL propres |
RewriteEngine On
:: /produit/42 → index.php?id=42 RewriteRule ^produit/([0-9]+)$ index.php?id=$1 [L,QSA]
:: /article/mon-titre → article.php?slug=mon-titre RewriteRule ^article/([a-z0-9-]+)$ article.php?slug=$1 [L,QSA]
:: Tout vers index.php (Front Controller) RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^ index.php [L,QSA]
:: QSA = Query String Append (conserver les paramètres) |
| Flags courants |
[L] :: Last — arrêter le traitement des règles [R=301] :: Redirect permanent (301) [R=302] :: Redirect temporaire (302) [NC] :: No Case — insensible à la casse [QSA] :: Query String Append [NE] :: No Escape — ne pas encoder les caractères spéciaux [PT] :: Pass Through — pour les Alias [F] :: Forbidden — retourner 403 [G] :: Gone — retourner 410 [E=var:val]:: Définir une variable d'environnement |
| Redirections |
| HTTP → HTTPS (redirection globale) |
RewriteEngine On RewriteCond %{HTTPS} !=on RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
:: Alternative (port 80) RewriteCond %{SERVER_PORT} 80 RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L] |
| www → sans www |
RewriteEngine On RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] RewriteRule ^ https://%1%{REQUEST_URI} [R=301,L] |
| sans www → www |
RewriteEngine On RewriteCond %{HTTP_HOST} !^www\. [NC] RewriteRule ^ https://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L] |
| Redirection permanente d'une page |
Redirect 301 /ancienne-page.html /nouvelle-page Redirect 301 /ancien-dossier/ /nouveau-dossier/
:: Ou avec RewriteRule RewriteRule ^ancienne-page\.html$ /nouvelle-page [R=301,L] |
| Redirection de domaine complet |
RewriteEngine On RewriteCond %{HTTP_HOST} ^ancien-domaine\.com$ [NC] RewriteRule ^ https://nouveau-domaine.com%{REQUEST_URI} [R=301,L] |
| Redirection extension (.php → sans extension) |
:: Accéder à /contact au lieu de /contact.php RewriteCond %{REQUEST_FILENAME} !-d RewriteCond %{REQUEST_FILENAME}\.php -f RewriteRule ^([^\.]+)$ $1.php [L] |
| Authentification HTTP (Basic Auth) |
| Protéger un dossier par mot de passe |
AuthType Basic AuthName "Zone privée" AuthUserFile /chemin/absolu/.htpasswd Require valid-user |
| Créer / gérer le fichier .htpasswd |
:: Créer (premier utilisateur) htpasswd -c /chemin/.htpasswd jean
:: Ajouter un utilisateur htpasswd /chemin/.htpasswd marie
:: Supprimer un utilisateur htpasswd -D /chemin/.htpasswd jean
:: Mot de passe en ligne de commande (non interactif) htpasswd -b /chemin/.htpasswd jean motdepasse |
| Autoriser un seul utilisateur |
AuthType Basic AuthName "Admin" AuthUserFile /chemin/.htpasswd Require user jean |
| Exclure certains fichiers de l'auth |
AuthType Basic AuthName "Zone privée" AuthUserFile /chemin/.htpasswd <Files "webhook.php"> Satisfy Any Allow from all </Files> Require valid-user |
| Contrôle d'Accès |
| Bloquer / autoriser par IP |
:: Apache 2.4+ Require ip 192.168.1.0/24 Require ip 10.0.0.1 Require all denied
:: Bloquer une IP <RequireAll> Require all granted Require not ip 192.168.1.5 </RequireAll> |
| Bloquer l'accès à certains fichiers |
<FilesMatch "\.(log|sql|bak|env|json|md|git)$"> Require all denied </FilesMatch>
:: Bloquer .htaccess et .htpasswd eux-mêmes <Files ~ "^\.ht"> Require all denied </Files> |
| Interdire le listage de dossier |
Options -Indexes
:: Ou pour activer le listage : Options +Indexes |
| Bloquer par User-Agent |
RewriteEngine On RewriteCond %{HTTP_USER_AGENT} (bot|spider|crawler) [NC] RewriteRule .* - [F,L] |
| Hotlinking (protéger les images) |
RewriteEngine On RewriteCond %{HTTP_REFERER} !^$ RewriteCond %{HTTP_REFERER} !^https?://(www\.)?mondomaine\.com [NC] RewriteRule \.(jpg|jpeg|png|gif|webp)$ - [F,L] |
| Headers de Sécurité |
| HSTS (forcer HTTPS) |
:: À activer uniquement quand HTTPS est stable ! Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" |
| X-Frame-Options (anti-clickjacking) |
Header always set X-Frame-Options "SAMEORIGIN" :: DENY = bloquer totalement :: SAMEORIGIN = autoriser uniquement le même domaine |
| X-Content-Type-Options |
Header always set X-Content-Type-Options "nosniff" |
| Referrer-Policy |
Header always set Referrer-Policy "strict-origin-when-cross-origin" |
| Content-Security-Policy (CSP) |
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:"
:: CSP permissive pour débuter : Header always set Content-Security-Policy "default-src *; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'" |
| Masquer la version Apache |
:: Dans httpd.conf (pas .htaccess) ServerSignature Off ServerTokens Prod |
| Supprimer/modifier un header |
Header unset X-Powered-By Header always unset X-Powered-By Header set X-Powered-By "PHP" |
| Cache Navigateur (mod_expires) |
| Configurer les durées de cache |
ExpiresActive On ExpiresDefault "access plus 1 month"
:: Par type de fichier ExpiresByType image/jpeg "access plus 1 year" ExpiresByType image/png "access plus 1 year" ExpiresByType image/webp "access plus 1 year" ExpiresByType image/svg+xml "access plus 1 year" ExpiresByType text/css "access plus 1 month" ExpiresByType application/javascript "access plus 1 month" ExpiresByType application/json "access plus 0 seconds" ExpiresByType text/html "access plus 0 seconds" |
| Cache-Control via Header |
<FilesMatch "\.(css|js|jpg|png|webp|woff2)$"> Header set Cache-Control "public, max-age=31536000, immutable" </FilesMatch>
<FilesMatch "\.(html|php)$"> Header set Cache-Control "no-cache, must-revalidate" </FilesMatch> |
| Compression (mod_deflate) |
| Activer la compression Gzip |
SetOutputFilter DEFLATE
:: Ou plus précis avec mod_deflate : AddOutputFilterByType DEFLATE text/html text/plain text/xml AddOutputFilterByType DEFLATE text/css application/javascript AddOutputFilterByType DEFLATE application/json application/xml AddOutputFilterByType DEFLATE image/svg+xml
:: Exclure les navigateurs anciens BrowserMatch ^Mozilla/4 gzip-only-text/html BrowserMatch ^Mozilla/4\.0[678] no-gzip BrowserMatch \bMSIE !no-gzip !gzip-only-text/html |
| Pages d'Erreur Personnalisées |
| Définir des pages d'erreur |
ErrorDocument 400 /erreurs/400.html ErrorDocument 401 /erreurs/401.html ErrorDocument 403 /erreurs/403.html ErrorDocument 404 /erreurs/404.php ErrorDocument 500 /erreurs/500.html
:: Page d'erreur avec message texte ErrorDocument 404 "Page non trouvée" |
| Paramètres PHP (si mod_php) |
| Modifier des directives PHP |
:: Taille des uploads php_value upload_max_filesize 64M php_value post_max_size 64M php_value max_execution_time 300 php_value memory_limit 256M
:: Masquer les erreurs PHP en production php_flag display_errors Off php_flag log_errors On php_value error_log /var/log/php_errors.log
:: Timezone php_value date.timezone Europe/Paris |
| CORS (Cross-Origin Resource Sharing) |
| Autoriser toutes les origines (API publique) |
Header always set Access-Control-Allow-Origin "*" Header always set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" Header always set Access-Control-Allow-Headers "Authorization, Content-Type, X-Requested-With" |
| Autoriser une origine spécifique |
SetEnvIf Origin "^https://app\.mondomaine\.com$" CORS_ORIGIN=$0 Header always set Access-Control-Allow-Origin "%{CORS_ORIGIN}e" env=CORS_ORIGIN Header always set Access-Control-Allow-Credentials "true" |
| Répondre aux requêtes OPTIONS (preflight) |
RewriteEngine On RewriteCond %{REQUEST_METHOD} OPTIONS RewriteRule .* - [R=204,L] |
| Types MIME et Encodage |
| Ajouter des types MIME |
AddType application/font-woff2 .woff2 AddType image/webp .webp AddType application/manifest+json .webmanifest AddType text/cache-manifest .appcache |
| Encodage des fichiers texte |
AddDefaultCharset UTF-8 AddCharset UTF-8 .html .php .css .js .json |