Quick note on HTTP security headers

I recently ran my site through securityheaders.com and realized I was missing a few useful headers. Here is the set I ended up adding to my Nginx config. The headers add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always; add_header Content-Security-Policy "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self'; font-src 'self';" always; What each one does: X-Content-Type-Options prevents browsers from guessing the MIME type of a response. Without this, a browser might interpret a text file as HTML and execute scripts in it. X-Frame-Options prevents your site from being embedded in an iframe on another domain. This mitigates clickjacking attacks. Referrer-Policy controls how much referrer information is sent when navigating away from your site. strict-origin-when-cross-origin is a sensible default. Permissions-Policy explicitly disables browser features your site does not use. A static blog has no need for camera or microphone access. Content-Security-Policy restricts where resources can be loaded from. The policy above only allows resources from the same origin, which is exactly right for a self-contained static site with no external dependencies. The always keyword Note the always parameter on each header. Without it, Nginx only adds headers to successful responses (2xx and 3xx). With always, headers are included on error pages too. This matters because error pages are also potential attack vectors. ...

March 8, 2026 · 2 min · Martin Lindqvist

Caching strategies for static sites

Serving a static site is fast by default, but proper caching headers make a noticeable difference for repeat visitors. These are the headers I configure and why. Cache-Control This is the primary header that controls caching behavior. For static assets like CSS, JS, images, and fonts: location ~* \.(css|js|png|jpg|jpeg|webp|svg|woff|woff2|ico)$ { add_header Cache-Control "public, max-age=2592000, immutable"; } max-age=2592000 is 30 days. The immutable directive tells the browser not to revalidate the resource even on a normal reload. This works well when your build tool hashes filenames — if the content changes, the filename changes, so the old cached version is never requested again. ...

December 14, 2025 · 2 min · Martin Lindqvist

Notes on running Nginx on a small VPS

I have been running Nginx on a 1 vCPU / 2 GB RAM VPS for a while now. These are my notes on configuration choices that make sense at this scale. The defaults are mostly fine Nginx is efficient out of the box. For a small site serving static files, you can run the default configuration and it will handle far more traffic than your server will ever see. But there are a few things worth adjusting. ...

October 9, 2025 · 4 min · Martin Lindqvist

Understanding TLS certificates and Let's Encrypt

When I first set up HTTPS on my server, I realized I did not fully understand what was happening behind the scenes. I knew I needed a certificate, and I knew Let’s Encrypt was free, but the details were fuzzy. So I dug into it. What TLS actually does TLS (Transport Layer Security) provides three things for a connection between a client and a server: Encryption. The data in transit cannot be read by anyone observing the network. Authentication. The client can verify it is talking to the intended server, not an impostor. Integrity. The data cannot be modified in transit without detection. When your browser connects to a site over HTTPS, a TLS handshake happens before any HTTP data is exchanged. During this handshake, the server presents its certificate. ...

September 28, 2025 · 5 min · Martin Lindqvist