Or: how to make a website more secure using a few headers with securityheaders.io.

More secure in what sense? You ensure the page loads only over HTTPS. You reduce the risk of MitM (Man-in-the-middle) attacks, clickjacking, and XSS. You need to know which web server you're using. If it's nginx and you can edit nginx.conf, it's just a few lines you add to the file. If you have Apache or can't edit the nginx configuration you'll need to use a .htaccess file. You'll add all headers inside the following tags:

The X-XSS-Protection header sets the XSS Auditor. Option 1 enables it and when the auditor detects a Reflected XSS, the page simply won't load at all. Option 0 disables this.

Header set X-XSS-Protection "1; mode=block"

X-Frame-Options sets conditions for embedding content in an iframe. In my case I'm disabling it entirely. You'll also encounter the SAMEORIGIN option (i.e. same domain, protocol, and port).

Header always append X-Frame-Options DENY

X-Content-Type-Options tells the browser not to interpret a file's MIME type on its own, but to leave that to the web server. This prevents Chrome from wanting to run js inside a txt file, for example — Google apparently knows best.

Header set X-Content-Type-Options nosniff

Referrer-Policy specifies under what conditions the web server should pass along where you came from. It mainly applies to transitions from HTTPS to HTTP. In this case I'm not passing the referrer at all.

Header set Referrer-Policy: strict-origin

Feature-Policy controls the page's access to device resources. I'm genuinely not interested in location, microphone, accelerometer… hence none.

Header always set Feature-Policy "geolocation 'none'; accelerometer 'none'; gyroscope 'none'; picture-in-picture 'none'; microphone 'none'; payment 'none'; sync-xhr 'none; usb 'none'; vr 'none'"

Content-Security-Policy is a tricky header. It specifies which sources content can be loaded from — scripts, styles, images, fonts… Anything you don't specify won't load.

Header set Content-Security-Policy "default-src 'self' https://www.ondrejsramek.cz; script-src 'self' 'unsafe-inline' https://ondrejsramek.cz/user/plugins/admin/themes/grav/js/ https://www.google-analytics.com https://srameko.report-uri.io; style-src 'self' 'unsafe-inline' https://www.ondrejsramek.cz https://fonts.googleapis.com; img-src 'self' data: https://www.ondrejsramek.cz https://www.gravatar.com https://www.google-analytics.com https://stats.g.doubleclick.net; font-src 'self' https://fonts.gstatic.com; connect-src 'self' https://getgrav.org/; object-src 'none' ; upgrade-insecure-requests; referrer no-referrer-when-downgrade; report-uri https://srameko.report-uri.io/r/default/csp/enforce; report-to https://srameko.report-uri.io/r/default/csp/enforce"

Public-Key-Pins tells the browser what the hashes of the certificates in the trust chain are — not just yours, but also the root and intermediate. This covers the case where yours expires. If the hashes don't match, the connection won't be made.

Header set Public-Key-Pins 'pin-sha256="pWduCAbO1PxeBmY5QyD300XFYJhIr4xOein6xdQk038="; pin-sha256="YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=";pin-sha256="Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys="; includeSubdomains'

Strict-Transport-Security specifies the number of seconds for which the page will load over HTTPS. Even if you tried HTTP, the browser would force HTTPS.

Header set Strict-Transport-Security 'max-age=31536000; includeSubDomains'

What problems will you run into? Google Analytics. If you're using ga.js, note that it can be loaded over both HTTP and HTTPS. You'll need to add ssl.google-analytics.com and www.google-analytics.com to your CSP. Don't forget that Analytics also uses images:

script-src https://ssl.google-analytics.com https://www.google-analytics.com; img-src https://www.google-analytics.com https://stats.g.doubleclick.net;

Another complication is the use of inline <script> tags — you'll need to transform them by creating an external script file and loading it instead. Also start using SRI (Subresource Integrity). For scripts and styles loaded from a CDN you specify a hash. If it doesn't match when the page loads, it won't be used. The Bootstrap and Cloudflare CDNs will give you the tag directly with the integrity attribute included. For your own files use the tool on report-uri.io, which will calculate the hash for you. If your web server doesn't support CORS SRI don't use it — modern browsers (current Firefox, Chrome) wouldn't be able to verify the hash match. Get all of this right and securityheaders.io will reward you with an A+ rating.

Other Related Posts:

DNS Security

DNS over HTTPs

68747470733a2f2f63646e2e6164746964792e6f72672f7075626c69632f416467756172642f436f6d6d6f6e2f616467756172645f686f6d652e676966
Když už jsem se pustil do generování certifikátu pro UDR, udělal jsem certifikát pro AdGuard Home. Aby DoH fungovalo je potřeba mít wildcard certifikát. Active24 má podporu DNS-01 přes API, takže vydání není žádný problém.
Výhoda DoH je v tom, že HTTPs je už otevřené (administrace...

8th Feb 2026