Header [condition] action name [value] [replacement] [env=...|expr=...] edits response headers; RequestHeader edits the request before the handler runs. set replaces, setifempty fills a gap, append extends a comma list, merge only if the token is missing, add makes a second header, unset removes every copy, and edit and edit* rewrite one or every regex match. account.php sends Cache-Control: no-cache, X-Debug: db=12ms user=42 and Set-Cookie: sid=a1b2c3; path=/; HttpOnly:
# /var/www/example/.htaccess: each mod_headers action on one PHP response
# Needs: mod_headers; AllowOverride FileInfo
Header setifempty Content-Security-Policy "default-src 'self'"
Header append Vary "Accept-Language"
Header merge Cache-Control "private"
Header merge Cache-Control "no-cache"
Header edit Set-Cookie "(?i);\s*httponly" "; HttpOnly; Secure; SameSite=Lax"
Header unset X-Debug
Header always set X-Always "on every status"
Header set X-Onsuccess "on 2xx only"curl 3,008 -sI on the page (minus Date, Server and Content-Type), then the X- headers of a 404:
HTTP/1.1 200 OK X-Always: on every status Cache-Control: no-cache, private Content-Security-Policy: default-src 'self' Vary: Accept-Language Set-Cookie: sid=a1b2c3; path=/; HttpOnly; Secure; SameSite=Lax X-Onsuccess: on 2xx only X-Always: on every status
Line 6 was a no-op. The 404 kept only line 9: onsuccess, the default, covers 2xx responses; always adds errors and redirects. They are two header tables, and the handler picks one. Under PHP-FPM (PHP-FPM via proxy_fcgi) the same file left X-Debug and the cookie alone and sent a second Cache-Control, because proxy_fcgi files backend headers under always; adding always to lines 3 to 8 fixed FPM and broke mod_php. For unset, write the line with and without always, which worked under both.