HTTP Security Headers WordPress per Agenzie: Configurazione Completa di HSTS, CSP e X-Frame-Options [2026]

2 settembre 202615 minSicurezza

HTTP Security Headers WordPress per Agenzie: Configurazione Completa di HSTS, CSP e X-Frame-Options [2026]

Gli HTTP security headers sono la prima linea di difesa che il server invia al browser prima ancora che venga renderizzato l’HTML. Su WordPress, la maggior parte delle agenzie ignora questi header o li configura male, rompendo Gutenberg, i widget a blocchi o i pagamenti Stripe. In questa guida configuriamo ogni header di sicurezza a livello di server (Nginx e Apache), mostrando cosa fa, perché serve e come testarlo. Se gestisci 10+ siti clienti, gli header di sicurezza sono parte del tuo hardening di base: non sono opzionali.

Questo articolo complementa la nostra guida alla sicurezza WordPress per agenzie e si integra con la guida alle performance WordPress e Core Web Vitals — gli header influenzano sia la sicurezza che la velocità percepita.

TL;DR — Cosa impari in questa guida

  • Cosa sono gli HTTP security headers e perché il browser li controlla prima di renderizzare la pagina
  • Configurazione completa per Nginx e Apache (.htaccess) di tutti gli header essenziali
  • Content-Security-Policy per WordPress: come evitarla e come configurarla senza rompere Gutenberg
  • HSTS con preload: quando ha senso e quando è pericoloso
  • Permissions-Policy: il header meno conosciuto che controlla camera, microfono, geolocalizzazione
  • Script PHP e WP-CLI per verificare gli header su 50+ siti in batch
  • Strumenti di test: securityheaders.com, Mozilla Observatory, curl

Cosa sono gli HTTP Security Headers e perché il browser li legge per primo

Quando il browser richiede una pagina WordPress, il server risponde con un insieme di header HTTP prima del corpo HTML. Alcuni di questi header — Content-Type, Cache-Control, Server — sono informativi. Altri dicono al browser come comportarsi con il contenuto: non eseguire script da fonti non autorizzate, non caricare la pagina in un iframe, connettersi solo via HTTPS per i prossimi 12 mesi.

Questi ultimi sono gli security headers. Il browser li legge e li applica prima di parseare l’HTML. Se un header dice “non eseguire script inline”, qualsiasi script inline nella pagina viene bloccato. Non c’è override lato client.

Secondo i dati di MDN Web Docs, il supporto browser per CSP è al 99.4% globale. HSTS è supportato al 98.7%. Non ci sono scuse per non configurarli.

Tabella: gli 8 security headers essenziali per WordPress

Header Cosa fa Livello rischio se assente
Strict-Transport-Security (HSTS) Forza HTTPS per un periodo configurabile Alto — downgrade attack possibile
Content-Security-Policy (CSP) Controlla le fonti di script, stili, immagini, connessioni Alto — XSS e data injection
X-Frame-Options Impedisce il rendering in iframe (clickjacking) Medio — clickjacking su form login
X-Content-Type-Options Disabilita MIME-sniffing del browser Medio — esecuzione di file caricati come script
Referrer-Policy Controlla quante informazioni referrer vengono inviate Basso — leak di URL con parametri sensibili
Permissions-Policy Abilita/disabilita API browser (camera, GPS, mic) Medio — accesso non autorizzato a sensori
Cross-Origin-Opener-Policy (COOP) Isola il contesto di navigazione da altri tab Medio — attacchi cross-origin
Cross-Origin-Resource-Policy (CORP) Controlla chi può caricare le risorse del sito Medio — Spectre-related attacks

Configurazione Nginx: il blocco security headers completo

Nginx è il server che raccomandiamo per WordPress in produzione. La configurazione degli header va nel blocco server del virtual host, non nel blocco http principale, perché diversi siti possono richiedere policy diverse (specialmente CSP).

Ecco la configurazione base che usiamo su tutti i siti clienti:

# /etc/nginx/sites-available/agencypilot.conf
# Nel blocco server, dopo le direttive location

# HSTS: 1 anno, includeSubDomains, preload
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

# X-Frame-Options: impedisce framing (deprecato se CSP frame-ancestors è impostato, ma lo teniamo per compatibilità)
add_header X-Frame-Options "SAMEORIGIN" always;

# X-Content-Type-Options: disabilita MIME sniffing
add_header X-Content-Type-Options "nosniff" always;

# Referrer-Policy: invia l'origine solo su same-origin
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

# Permissions-Policy: disabilita tutto tranne what serve
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()" always;

# CORP: solo same-site può caricare risorse
add_header Cross-Origin-Resource-Policy "same-site" always;

# COOP: isola il contesto
add_header Cross-Origin-Opener-Policy "same-origin" always;

# CSP: vedi sezione dedicata sotto
# add_header Content-Security-Policy "..." always;

La direttiva always è critica: assicura che gli header vengano inviati anche sulle risposte di errore (403, 404, 500). Senza always, Nginx invia gli header solo sulle risposte 200 e 301, lasciando le pagine di errore scoperte.

Attenzione a add_header e l’ereditarietà Nginx

Un errore comune: se hai una direttiva add_header dentro un blocco location, Nginx non eredita gli add_header dal blocco server. Devi ripeterli o usare include con un file shared:

# /etc/nginx/snippets/security-headers.conf
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()" always;
add_header Cross-Origin-Resource-Policy "same-site" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;

# Nel virtual host:
server {
    listen 443 ssl http2;
    server_name agencypilot.it;
    
    include snippets/security-headers.conf;
    
    location / {
        # Se aggiungi un add_header qui, ripeti include
        include snippets/security-headers.conf;
        try_files $uri $uri/ /index.php?$args;
    }
}

Nella nostra esperienza su 50+ siti, l’errore dell’ereditarietà add_header è la causa numero 1 di header mancanti. Lo abbiamo visto su siti con Cloudflare davanti: il CDN mostrava grade A su securityheaders.com ma il origin server non inviava nulla sulle risposte 404.

Configurazione Apache (.htaccess): per hosting shared

Non tutte le agenzie hanno clienti su VPS con Nginx. Molti siti sono su hosting shared con Apache. La configurazione via .htaccess funziona ma ha un costo di performance: Apache rilegge il file a ogni richiesta. Per siti ad alto traffico, meglio configurare gli header nel virtual host di Apache direttamente.

# .htaccess — nella root di WordPress

<IfModule mod_headers.c>
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
    Header always set X-Frame-Options "SAMEORIGIN"
    Header always set X-Content-Type-Options "nosniff"
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
    Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()"
    Header always set Cross-Origin-Resource-Policy "same-site"
    Header always set Cross-Origin-Opener-Policy "same-origin"
</IfModule>

Se il modulo mod_headers non è abilitato, queste direttive vengono ignorate silenziosamente. Verifica con a2enmod headers && systemctl restart apache2 sui server che controlli direttamente, o chiedi all’hosting provider.

Content-Security-Policy per WordPress: l’header difficile

CSP è l’header più potente e più difficile da configurare su WordPress. Il problema: WordPress core, plugin e temi injectano script inline, stili inline, e font da CDN senza alcun controllo centralizzato. Una CSP rigorosa blocca tutto questo e rompe il sito.

La strategia che raccomandiamo è a fasi: partire con Content-Security-Policy-Report-Only per raccogliere le violazioni, poi passare a CSP enforcement quando hai whitelistato tutte le fonti.

Fase 1: CSP Report-Only

# Nginx
add_header Content-Security-Policy-Report-Only "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://www.googletagmanager.com https://www.google-analytics.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self' https://www.google-analytics.com; report-uri /csp-report;" always;

Questa CSP in modalità report-only non blocca nulla: registra le violazioni su /csp-report. Crea un endpoint in WordPress per raccogliere i report:

// functions.php — endpoint per CSP reports
add_action('init', function() {
    add_rewrite_rule('^csp-report/?$', 'index.php?csp_report=1', 'top');
});

add_filter('query_vars', function($vars) {
    $vars[] = 'csp_report';
    return $vars;
});

add_action('template_redirect', function() {
    if (get_query_var('csp_report')) {
        $report = file_get_contents('php://input');
        // Log su error_log o invia a Sentry
        error_log('CSP Violation: ' . $report);
        http_response_code(204);
        exit;
    }
});

Dopo 7-14 giorni di raccolta report, controlla i log. Le violazioni ricorrenti indicano fonti che devi whitelistare. Quando non ci sono più violazioni, passa alla fase 2.

Fase 2: CSP Enforcement

# Sostituisci Report-Only con enforcement
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://www.google-analytics.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data: https:; connect-src 'self' https://www.google-analytics.com; frame-ancestors 'self'; base-uri 'self'; form-action 'self';" always;

Perché 'unsafe-inline' è quasi obbligatorio su WordPress

WordPress core stampa script e stili inline in wp_head() e wp_footer(). I plugin fanno lo stesso. Rimuovere 'unsafe-inline' da script-src rompe Gutenberg, WooCommerce, e la maggior parte dei page builder. La soluzione corretta sarebbe usare i nonce CSP generati per ogni richiesta, ma questo richiede un plugin dedicato o un tema custom che filtri tutti gli asset registrati.

Nella nostra esperienza, 'unsafe-inline' su script-src e style-src è il compromesso realistico per la maggior parte dei siti WordPress. La CSP protegge comunque dalle fonti esterne non whitelistate, che è dove avvengono la maggior parte degli attacchi XSS.

HSTS e preload: quando ha senso e quando è pericoloso

HSTS (HTTP Strict Transport Security) dice al browser: “per i prossimi N secondi, connettiti a questo dominio solo via HTTPS, ignora qualsiasi redirect HTTP”. Il browser memorizza questa direttiva e la applica prima ancora di fare la richiesta DNS.

La direttiva preload è un passo ulteriore: il dominio viene inserito nella lista preload di Chrome, hardcoded nel browser. Questo significa che anche la prima visita userà HTTPS, senza bisogno di un redirect 301.

Ma c’è un problema. Se in futuro vuoi spostare il dominio su HTTP (non succede quasi mai, ma capita durante test o migrazioni), HSTS renderà il dominio inaccessibile via HTTP per tutti i browser che hanno memorizzato la direttiva. Con preload, la rimozione dalla lista di Chrome richiede settimane o mesi.

Quando usare HSTS preload (e quando NO)

Scenario HSTS preload? Motivo
Sito produzione stabile su HTTPS Sì Prevenzione downgrade attack fin dalla prima visita
Sito con sottodomini su HTTP No includeSubDomains rompe i sottodomini HTTP
Staging su dominio temporaneo No Se cambi dominio, HSTS persiste nel browser
Sito che usa CDN senza HTTPS su origin No HSTS forza HTTPS anche per API calls al origin
Migrazione in corso da HTTP a HTTPS Aspetta Configura HSTS solo dopo che il redirect 301 funziona da 2+ settimane

Il valore max-age minimo per la lista preload è 31536000 (1 anno). Ridurre il valore dopo essere stati nella lista preload non rimuove il dominio. Pensaci prima di attivare preload.

X-Frame-Options vs CSP frame-ancestors: quale usare

X-Frame-Options è il header storico per prevenire clickjacking. Ha due valori: DENY (nessuno può embeddare la pagina in iframe) e SAMEORIGIN (solo lo stesso dominio può).

Il problema: X-Frame-Options non supporta whitelist di domini multipli. Se hai bisogno di embeddare la pagina su agencypilot.it E su client.example.com, non puoi farlo con X-Frame-Options.

CSP frame-ancestors risolve questo:

Content-Security-Policy: frame-ancestors 'self' https://client.example.com;

Quando frame-ancestors è presente nella CSP, il browser ignora X-Frame-Options. Ma non tutti i browser implementano questo override correttamente (versioni vecchie di Safari hanno avuto bug qui). La nostra raccomandazione: mantieni entrambi. X-Frame-Options: SAMEORIGIN come fallback, CSP frame-ancestors per il controllo granulare.

Permissions-Policy: il header che blocca camera e microfono

Permissions-Policy (ex Feature-Policy) è il header meno conosciuto ma più utile per WordPress. Controlla quali API browser il sito può usare: camera, microfono, geolocalizzazione, pagamenti, vibrazione, autoplay.

Per un sito WordPress standard che non usa camera o microfono, la configurazione è aggressiva:

Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()

Il valore () significa “nessun sito, incluso self, può usare questa API”. Se un plugin viene compromesso e tenta di accedere alla camera, il browser blocca la richiesta a livello di policy.

Se il sito usa WooCommerce e ha un checkout con Stripe, payment=() potrebbe rompere il 3D Secure. In quel caso usa payment=(self) per permettere i pagamenti solo dal dominio stesso.

Referrer-Policy: proteggere gli URL con parametri sensibili

Quando un utente clicca un link dal tuo sito WordPress a un sito esterno, il browser invia l’URL di origine come Referer header. Se l’URL contiene parametri (token di reset password, ID ordine, query di ricerca), questi dati finiscono nei log del sito destinazione.

La policy strict-origin-when-cross-origin è il default dei browser moderni, ma verificarla a livello di server è buona pratica:

Referrer-Policy: strict-origin-when-cross-origin

Questa invia l’origine completa su same-origin, ma solo il dominio (senza path e query string) su cross-origin, e niente su downgrade HTTPS→HTTP.

Verificare gli header: curl, securityheaders.com, Mozilla Observatory

Test rapido con curl

curl -sI https://agencypilot.it | grep -iE 'strict-transport|x-frame|x-content|referrer|permissions|content-security|cross-origin'

Output atteso:

strict-transport-security: max-age=31536000; includeSubDomains; preload
x-frame-options: SAMEORIGIN
x-content-type-options: nosniff
referrer-policy: strict-origin-when-cross-origin
permissions-policy: camera=(), microphone=(), geolocation=(), payment=()
cross-origin-resource-policy: same-site
cross-origin-opener-policy: same-origin
content-security-policy: default-src 'self'; ...

securityheaders.com

securityheaders.com di Scott Helme è il tool di riferimento. Inserisci l’URL e ottieni un grade da A+ a F. Il tool verifica solo gli header di sicurezza, non il contenuto. Un grade A+ richiede: HSTS, CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy.

Mozilla Observatory

Mozilla Observatory è più rigoroso: testa anche CORS, COOP, CORP, e cookie flags. Un grade A qui significa che il sito ha una configurazione di sicurezza headers praticamente perfetta.

Script WP-CLI per verificare gli header su 50+ siti

Se gestisci molti siti clienti, verificare ogni sito manualmente è impossibile. Abbiamo scritto uno script che controlla gli header via WP-CLI su tutti i siti di un’installazione multisite o su una lista di domini:

#!/bin/bash
# check-security-headers.sh
# Usage: ./check-security-headers.sh sites.txt
# sites.txt: un dominio per riga

EXPECTED_HEADERS=(
    "strict-transport-security"
    "x-frame-options"
    "x-content-type-options"
    "referrer-policy"
    "permissions-policy"
    "content-security-policy"
)

while IFS= read -r domain; do
    echo "=== $domain ==="
    headers=$(curl -sI "https://$domain" 2>/dev/null)
    
    for header in "${EXPECTED_HEADERS[@]}"; do
        if echo "$headers" | grep -qi "$header"; then
            echo "  ✓ $header"
        else
            echo "  ✗ $header MISSING"
        fi
    done
    echo ""
done < "$1"

Salva lo script in ~/scripts/check-security-headers.sh, crea un file sites.txt con tutti i domini clienti, e lancia:

chmod +x ~/scripts/check-security-headers.sh
~/scripts/check-security-headers.sh /path/to/sites.txt | tee header-report-$(date +%Y%m%d).txt

Nella nostra gestione di 50+ siti, questo script gira ogni lunedì mattina. I siti con header mancanti vengono segnalati nel report settimanale che mandiamo ai clienti. Si integra con il workflow descritto nella nostra guida alla gestione multi-sito WordPress.

Cloudflare e gli header di sicurezza: attenzione ai doppioni

Se usi Cloudflare davanti a WordPress, Cloudflare può aggiungere alcuni header (HSTS, X-Frame-Options) dal pannello di controllo. Se li configuri anche sul origin server, il browser riceve header duplicati. Il comportamento del browser con header duplicati non è definito in modo standard: alcuni browser usano il primo valore, altri l’ultimo, altri li concatenano.

La regola: scegli un layer per gli header di sicurezza. O Cloudflare, o origin server. Non entrambi. Noi raccomandiamo origin server per CSP (che cambia per sito) e Cloudflare per HSTS (che è uniforme su tutti i domini).

Cloudflare Page Rules possono iniettare header. Ma la nuova raccomandazione è usare Cloudflare Rulesets nel pannello Rules, che supporta condizioni più granulari e non richiede un piano a pagamento superiore.

Header e Core Web Vitals: impatto sulle performance

Gli header di sicurezza hanno un impatto minimo sulle performance. Strict-Transport-Security riduce i redirect HTTP→HTTPS sulla prima visita (risparmio di 200-300ms). Content-Security-Policy ha un overhead di parse microscopico. Permissions-Policy può migliorare le performance bloccando API pesanti (camera, WebRTC) che altrimenti verrebbero inizializzate.

L’header che più influisce sulle performance è Cross-Origin-Embedder-Policy (COEP). COEP require-corp forza il browser a verificare ogni risorsa cross-origin con CORP o CORS headers. Se il tuo sito embedda immagini da CDN senza Cross-Origin-Resource-Policy, queste immagini vengono bloccate. Questo rompe anche Google Maps embed, YouTube, e molti widget.

Per la maggior parte dei siti WordPress, COEP non è raccomandato a meno che non tu non abbia bisogno di SharedArrayBuffer (Web Workers avanzati, ma quasi mai il caso su WordPress).

FAQ — HTTP Security Headers WordPress

Cosa sono gli HTTP security headers?

Sono header di risposta HTTP che il server invia al browser per istruirlo su come gestire il contenuto della pagina in modo sicuro. Gli esempi principali sono Content-Security-Policy (che controlla le fonti di script e stili), Strict-Transport-Security (che forza HTTPS), e X-Frame-Options (che previene il clickjacking). Il browser legge questi header prima di renderizzare la pagina.

WordPress ha bisogno di security headers configurati?

Sì. WordPress core non imposta nessun security header di default. Il file .htaccess generato da WordPress contiene solo direttive per i pretty permalink, non header di sicurezza. Plugin come Wordfence o Solid Security possono aggiungerli, ma la configurazione a livello di server (Nginx o Apache) è più affidabile e performante.

Content-Security-Policy rompe WordPress?

Una CSP rigorosa può rompere WordPress perché il core e molti plugin injectano script inline senza nonce. La soluzione è partire con Content-Security-Policy-Report-Only per raccogliere le violazioni, whitelistare le fonti necessarie, e poi passare a enforcement. Mantenere 'unsafe-inline' su script-src e style-src è il compromesso realistico per la maggior parte dei siti.

Come testo gli security headers del mio sito WordPress?

I tre metodi principali sono: curl -sI https://tuo-sito.it per verificare gli header via terminale, securityheaders.com per un grade da A+ a F, e Mozilla Observatory per un’analisi più approfondita che include anche CORS, cookie, e COOP/CORP. Per gestire 50+ siti, usa lo script bash che abbiamo fornito sopra con una lista di domini.

HSTS preload è sicuro per WordPress?

HSTS preload è sicuro se il sito è stabile su HTTPS con un certificato valido rinnovato automaticamente. Non è sicuro se prevedi di spostare il dominio, usare sottodomini su HTTP, o se il certificato potrebbe scadere. La rimozione dalla lista preload di Chrome richiede settimane. Attiva preload solo su domini produzione che resteranno su HTTPS per sempre.

Quali header di sicurezza sono obbligatori per la PA italiana?

La Pubblica Amministrazione italiana deve seguire le linee guida AGID sulla sicurezza. Le specifiche tecniche richiedono HSTS, X-Frame-Options, X-Content-Type-Options, e CSP come minimo. La nostra guida su WordPress per la PA copre i requisiti completi di conformità.

Gestisci i siti WordPress dei tuoi clienti?

AgencyPilot ti dà report AI, uptime monitoring, backup e portale clienti in un’unica dashboard. Gratis per 3 siti.

Prova gratis
Leggi anche
Tutti gli articoli
Tutti gli articoli