<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nils Eriksson</title>
    <description>The latest articles on DEV Community by Nils Eriksson (@nilseriksson92).</description>
    <link>https://dev.to/nilseriksson92</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3977753%2Ff3c4c6b9-a1bc-4e77-8027-682840a40463.png</url>
      <title>DEV Community: Nils Eriksson</title>
      <link>https://dev.to/nilseriksson92</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nilseriksson92"/>
    <language>en</language>
    <item>
      <title>TLS Configuration in 2026: What "Secure" Actually Means Now</title>
      <dc:creator>Nils Eriksson</dc:creator>
      <pubDate>Wed, 10 Jun 2026 13:04:37 +0000</pubDate>
      <link>https://dev.to/nilseriksson92/tls-configuration-in-2026-what-secure-actually-means-now-192d</link>
      <guid>https://dev.to/nilseriksson92/tls-configuration-in-2026-what-secure-actually-means-now-192d</guid>
      <description>&lt;h1&gt;
  
  
  TLS Configuration in 2026: What "Secure" Actually Means Now
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;Meta: Having SSL/TLS is not a security posture. The green padlock tells users nothing useful. This article covers what "secure" actually means in 2026: why TLS 1.0 and 1.1 are dead, what TLS 1.3 actually improved, certificate management, cipher suites, HSTS, NIS2 context, and practical nginx/Apache configs.&lt;/p&gt;

&lt;p&gt;Keyword: TLS configuration 2026, TLS 1.3 best practices, SSL TLS setup, NIS2 cryptographic requirements&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;A client showed me their SSL certificate last week. Green padlock. Certificate valid. Expiry date: 2027.&lt;/p&gt;

&lt;p&gt;I checked the TLS version their server was advertising: TLS 1.0.&lt;/p&gt;

&lt;p&gt;I told them they had a liability, not a security measure.&lt;/p&gt;

&lt;p&gt;They looked confused. "But the lock is green," they said.&lt;/p&gt;

&lt;p&gt;Yeah. And that's the problem with how most people think about SSL/TLS.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Green Padlock Is Not Security
&lt;/h2&gt;

&lt;p&gt;Let me be direct: having HTTPS is not a security posture. It's a basic requirement. Like having a front door. A locked front door keeps out opportunistic thieves, but it doesn't stop an intruder who knows the lock is broken.&lt;/p&gt;

&lt;p&gt;The green padlock tells users: "The connection to this server is encrypted." That's true. It doesn't tell them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Whether the encryption is any good&lt;/li&gt;
&lt;li&gt;Whether the server is who it claims to be&lt;/li&gt;
&lt;li&gt;Whether the site has other vulnerabilities&lt;/li&gt;
&lt;li&gt;Whether the cipher suites are modern or ancient&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A visitor sees the lock and feels safe. That feeling is mostly marketing.&lt;/p&gt;

&lt;p&gt;Your job is to make the lock actually mean something.&lt;/p&gt;

&lt;h2&gt;
  
  
  TLS 1.0 and 1.1 Are Dead. Stop Running Them.
&lt;/h2&gt;

&lt;p&gt;TLS 1.0 was ratified in 1999. That's 27 years ago. In cryptography terms, it's ancient.&lt;/p&gt;

&lt;p&gt;TLS 1.1 came out in 2006. Still ancient.&lt;/p&gt;

&lt;p&gt;Both have known attacks. Both are weak. And yet—I still see them in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why they're bad:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TLS 1.0 and 1.1 use MD5 and SHA1 for message authentication, both of which are cryptographically broken.&lt;/li&gt;
&lt;li&gt;They don't protect against various padding attacks.&lt;/li&gt;
&lt;li&gt;They require cipher suites that modern attackers can break in reasonable time.&lt;/li&gt;
&lt;li&gt;Every major browser has deprecated them or is actively phasing them out.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why people still run them:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Usually legacy. An old device (POS terminal, printer, sensor) only speaks TLS 1.0. So they support it. The device works. Problem solved—until an attacker uses the device as an entry point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to do:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Disable TLS 1.0 and 1.1. I don't care if you lose 0.01% of traffic from devices that can't upgrade. That 0.01% is a security liability.&lt;/p&gt;

&lt;p&gt;If you have a device that only speaks TLS 1.0, you have two options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Upgrade the device.&lt;/li&gt;
&lt;li&gt;Isolate it on its own connection, not the public internet.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There is no third option where it's acceptable to support TLS 1.0 in 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  TLS 1.2: The Status Quo
&lt;/h2&gt;

&lt;p&gt;TLS 1.2 has been around since 2008. It's solid. Still widely used. Still secure if configured correctly.&lt;/p&gt;

&lt;p&gt;The keyword is &lt;em&gt;if configured correctly&lt;/em&gt;. Because TLS 1.2 is flexible, and flexibility means there are wrong ways to use it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What can go wrong:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Using CBC ciphers without AEAD (authenticated encryption with associated data).&lt;/li&gt;
&lt;li&gt;Supporting cipher suites that use DES, RC4, or other broken ciphers.&lt;/li&gt;
&lt;li&gt;Not enforcing perfect forward secrecy (PFS).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The good news: if you're enforcing TLS 1.2 &lt;em&gt;or&lt;/em&gt; TLS 1.3 and using modern cipher suites, you're fine. You're not using TLS 1.2 for the coolness factor. You're using it because some people on old systems need it.&lt;/p&gt;

&lt;h2&gt;
  
  
  TLS 1.3: What Actually Changed
&lt;/h2&gt;

&lt;p&gt;TLS 1.3 shipped in 2018. It's been eight years. Most browsers support it. Most servers support it. If you're not using it, you should be asking why.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What TLS 1.3 improved:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reduced handshake:&lt;/strong&gt; TLS 1.2 takes two round trips to establish encryption. TLS 1.3 does it in one. This matters for latency, especially on high-latency connections.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Removed broken ciphers:&lt;/strong&gt; TLS 1.3 only supports AEAD cipher suites. No more negotiation over broken algorithms. No cipher suite downgrade attacks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Perfect Forward Secrecy by default:&lt;/strong&gt; In TLS 1.3, every session gets a unique key. Even if someone steals your private key tomorrow, they can't decrypt past sessions. This should have been default all along.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;0-RTT (kind of):&lt;/strong&gt; You can send data in the initial handshake. This is great for latency. It's also dangerous if misused, but it's there.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Removed renegotiation:&lt;/strong&gt; TLS 1.2 allowed mid-connection renegotiation, which opened attack vectors. Gone in TLS 1.3.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The practical benefit:&lt;/strong&gt; TLS 1.3 is faster, more secure, and harder to misconfigure. If you can support it, you should.&lt;/p&gt;

&lt;h2&gt;
  
  
  Certificates: The Other Half of the Equation
&lt;/h2&gt;

&lt;p&gt;A certificate proves that you own the domain. That's it. It doesn't prove your server is secure. It doesn't prove you're not exfiltrating data. It just proves: yes, this domain belongs to me.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Types of certificates:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Domain Validated (DV):&lt;/strong&gt; Cheap, easy. The CA checks that you can receive email or modify DNS for the domain. That's all the verification they do. This is fine for most sites. It's what Let's Encrypt issues.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Organization Validated (OV):&lt;/strong&gt; More expensive. The CA verifies that your organization exists. Browsers display "organization" info. This doesn't make you more secure. Most people don't see it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Extended Validation (EV):&lt;/strong&gt; Most expensive. The CA does extensive verification. Browsers &lt;em&gt;used to&lt;/em&gt; show a green bar with your company name. That's mostly gone now because users didn't care. Don't bother.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Wildcard certificates:&lt;/strong&gt; One certificate for multiple subdomains. &lt;code&gt;*.example.com&lt;/code&gt; covers &lt;code&gt;api.example.com&lt;/code&gt;, &lt;code&gt;cdn.example.com&lt;/code&gt;, etc. Be careful with these—if one subdomain is compromised, the attacker has a valid certificate for all of them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Practical recommendation:&lt;/strong&gt; Use DV certificates from Let's Encrypt. Free, automated renewal, plenty of entropy. Wildcard if you have lots of subdomains.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cipher Suites: The Meat of the Config
&lt;/h2&gt;

&lt;p&gt;A cipher suite is the combination of algorithms that TLS uses. In TLS 1.2, you can choose from dozens. In TLS 1.3, the choice is much more limited (in a good way).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For TLS 1.3, use this:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TLS_AES_256_GCM_SHA384
TLS_AES_128_GCM_SHA256
TLS_CHACHA20_POLY1305_SHA256
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. All three are solid. All three support perfect forward secrecy. Modern browsers support all three.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For TLS 1.2, if you have to:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ECDHE-ECDSA-AES256-GCM-SHA384
ECDHE-RSA-AES256-GCM-SHA384
ECDHE-ECDSA-AES128-GCM-SHA256
ECDHE-RSA-AES128-GCM-SHA256
ECDHE-ECDSA-CHACHA20-POLY1305
ECDHE-RSA-CHACHA20-POLY1305
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See the ECDHE? That's elliptic curve Diffie-Hellman Ephemeral. The ephemeral part means the key is used once and discarded. That's perfect forward secrecy.&lt;/p&gt;

&lt;p&gt;Anything without ECDHE (DHE instead, or no PFS at all) is weaker. Don't use it.&lt;/p&gt;

&lt;h2&gt;
  
  
  HSTS: Permanent Redirect
&lt;/h2&gt;

&lt;p&gt;HSTS (Strict-Transport-Security) tells the browser: "Always use HTTPS for this domain. No exceptions. Even if the user types HTTP, force HTTPS."&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What this does:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;max-age=31536000&lt;/code&gt; — Remember this for one year (in seconds).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;includeSubDomains&lt;/code&gt; — Apply to all subdomains.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;preload&lt;/code&gt; — Include me in the HSTS preload list (a hardcoded list in browsers).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Without HSTS, an attacker can intercept the initial HTTP request, before the browser gets redirected to HTTPS. HSTS kills that attack—the browser refuses to connect via HTTP at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The warning:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Preload is optional but permanent. Once you're in the list, you can't get out quickly. If you enable HSTS with &lt;code&gt;preload&lt;/code&gt; and then decide you need to serve some subdomains over HTTP (for legacy stuff), you're stuck. Browsers won't let you.&lt;/p&gt;

&lt;p&gt;Only add &lt;code&gt;preload&lt;/code&gt; if you're 100% sure you want HTTPS everywhere, forever.&lt;/p&gt;

&lt;p&gt;Also: &lt;code&gt;includeSubDomains&lt;/code&gt; is risky if you have any subdomain that doesn't support HTTPS. &lt;code&gt;mail.example.com&lt;/code&gt; still on HTTP? HSTS will break it.&lt;/p&gt;

&lt;p&gt;I've seen companies destroy services by enabling HSTS incorrectly. Get your HTTPS infrastructure solid first. Then enable HSTS.&lt;/p&gt;

&lt;h2&gt;
  
  
  Certificates: Expiry and Monitoring
&lt;/h2&gt;

&lt;p&gt;Certificates expire. You need to notice before they do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When a certificate expires, the browser shows a warning. The user thinks the site is compromised (or just broken). Traffic drops. Users go somewhere else. You miss the outage because nobody tells you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to prevent it:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Automated renewal:&lt;/strong&gt; Let's Encrypt + certbot automatically renews certificates before expiry. Set it and forget it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Monitoring:&lt;/strong&gt; Use a service that checks your certificate expiry and alerts you. There are free options (e.g., checking cert transparency logs, using &lt;code&gt;curl&lt;/code&gt; to extract certificate dates).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Certificate Transparency:&lt;/strong&gt; Your certificates are logged in CT logs (by law, in most jurisdictions). You can check what certificates exist for your domain. Unexpected certificate? That's a sign of account compromise.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  NIS2 Context
&lt;/h2&gt;

&lt;p&gt;The NIS2 directive (Network and Information Security Directive 2) came into effect for critical infrastructure in late 2024. For most SMBs, it's not directly applicable yet. But it's worth understanding the direction.&lt;/p&gt;

&lt;p&gt;NIS2 requires "encryption of data in transit" for critical systems. It doesn't prescribe TLS. But TLS is the de-facto standard. NIS2 expects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Encryption of data in transit (TLS).&lt;/li&gt;
&lt;li&gt;Encryption of data at rest (separate topic, but included).&lt;/li&gt;
&lt;li&gt;Regular security assessments.&lt;/li&gt;
&lt;li&gt;Incident reporting.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For your purposes: if you're encrypting data in transit with TLS 1.2+ using strong ciphers, you're compliant. If you're running TLS 1.0 or weak ciphers, you're not.&lt;/p&gt;

&lt;p&gt;The directive is general enough that it doesn't prescribe specifics. Your security posture matters more than the certificate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical: Nginx Config
&lt;/h2&gt;

&lt;p&gt;Here's a reasonable nginx configuration for 2026:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt; &lt;span class="s"&gt;ssl&lt;/span&gt; &lt;span class="s"&gt;http2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="s"&gt;[::]:443&lt;/span&gt; &lt;span class="s"&gt;ssl&lt;/span&gt; &lt;span class="s"&gt;http2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;example.com&lt;/span&gt; &lt;span class="s"&gt;www.example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# Certificates (from Let's Encrypt)&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_certificate&lt;/span&gt; &lt;span class="n"&gt;/etc/letsencrypt/live/example.com/fullchain.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_certificate_key&lt;/span&gt; &lt;span class="n"&gt;/etc/letsencrypt/live/example.com/privkey.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# TLS protocol: 1.2 and 1.3 only&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_protocols&lt;/span&gt; &lt;span class="s"&gt;TLSv1.2&lt;/span&gt; &lt;span class="s"&gt;TLSv1.3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# Cipher suites: strong ciphers first&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_ciphers&lt;/span&gt; &lt;span class="s"&gt;'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_prefer_server_ciphers&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# Perfect Forward Secrecy&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_dhparam&lt;/span&gt; &lt;span class="n"&gt;/etc/ssl/dhparam.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_ecdh_curve&lt;/span&gt; &lt;span class="s"&gt;secp384r1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# Session settings&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_session_cache&lt;/span&gt; &lt;span class="s"&gt;shared:SSL:10m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_session_timeout&lt;/span&gt; &lt;span class="mi"&gt;10m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_session_tickets&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# HSTS (be careful with this)&lt;/span&gt;
    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Strict-Transport-Security&lt;/span&gt; &lt;span class="s"&gt;"max-age=31536000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="kn"&gt;includeSubDomains&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="kn"&gt;preload"&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# Other security headers&lt;/span&gt;
    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;X-Content-Type-Options&lt;/span&gt; &lt;span class="s"&gt;"nosniff"&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;X-Frame-Options&lt;/span&gt; &lt;span class="s"&gt;"DENY"&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Referrer-Policy&lt;/span&gt; &lt;span class="s"&gt;"strict-origin-when-cross-origin"&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# Your app config here&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://backend&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Redirect HTTP to HTTPS&lt;/span&gt;
&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="s"&gt;[::]:80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;example.com&lt;/span&gt; &lt;span class="s"&gt;www.example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;301&lt;/span&gt; &lt;span class="s"&gt;https://&lt;/span&gt;&lt;span class="nv"&gt;$server_name$request_uri&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generate dhparam once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl dhparam &lt;span class="nt"&gt;-out&lt;/span&gt; /etc/ssl/dhparam.pem 2048
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Practical: Apache Config
&lt;/h2&gt;

&lt;p&gt;For Apache, use this in your VirtualHost or .htaccess:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nl"&gt;VirtualHost&lt;/span&gt;&lt;span class="sr"&gt; *:443&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="nc"&gt;ServerName&lt;/span&gt; example.com
    &lt;span class="nc"&gt;ServerAlias&lt;/span&gt; www.example.com

    &lt;span class="nc"&gt;SSLEngine&lt;/span&gt; &lt;span class="ss"&gt;on&lt;/span&gt;
    &lt;span class="nc"&gt;SSLCertificateFile&lt;/span&gt; /etc/letsencrypt/live/example.com/fullchain.pem
    &lt;span class="nc"&gt;SSLCertificateKeyFile&lt;/span&gt; /etc/letsencrypt/live/example.com/privkey.pem

    &lt;span class="c"&gt;# TLS protocols&lt;/span&gt;
    &lt;span class="nc"&gt;SSLProtocol&lt;/span&gt; TLSv1.2 TLSv1.3

    &lt;span class="c"&gt;# Cipher suites&lt;/span&gt;
    &lt;span class="nc"&gt;SSLCipherSuite&lt;/span&gt; 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256'
    &lt;span class="nc"&gt;SSLHonorCipherOrder&lt;/span&gt; &lt;span class="ss"&gt;On&lt;/span&gt;

    &lt;span class="c"&gt;# DH parameters&lt;/span&gt;
    &lt;span class="nc"&gt;SSLOpenSSLConfCmd&lt;/span&gt; DHParameters "/etc/ssl/dhparam.pem"

    &lt;span class="c"&gt;# Session&lt;/span&gt;
    &lt;span class="nc"&gt;SSLSessionCache&lt;/span&gt; "shmcb:logs/ssl_scache(512000)"
    &lt;span class="nc"&gt;SSLSessionCacheTimeout&lt;/span&gt; 300

    &lt;span class="c"&gt;# HSTS&lt;/span&gt;
    &lt;span class="nc"&gt;Header&lt;/span&gt; &lt;span class="ss"&gt;always&lt;/span&gt; &lt;span class="ss"&gt;set&lt;/span&gt; Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"

    &lt;span class="c"&gt;# Other headers&lt;/span&gt;
    &lt;span class="nc"&gt;Header&lt;/span&gt; &lt;span class="ss"&gt;always&lt;/span&gt; &lt;span class="ss"&gt;set&lt;/span&gt; X-Content-Type-Options "nosniff"
    &lt;span class="nc"&gt;Header&lt;/span&gt; &lt;span class="ss"&gt;always&lt;/span&gt; &lt;span class="ss"&gt;set&lt;/span&gt; X-Frame-Options "DENY"
    &lt;span class="nc"&gt;Header&lt;/span&gt; &lt;span class="ss"&gt;always&lt;/span&gt; &lt;span class="ss"&gt;set&lt;/span&gt; Referrer-Policy "strict-origin-when-cross-origin"

    &lt;span class="c"&gt;# Your config here&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nl"&gt;VirtualHost&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;
&lt;span class="c"&gt;# Redirect HTTP to HTTPS&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nl"&gt;VirtualHost&lt;/span&gt;&lt;span class="sr"&gt; *:80&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="nc"&gt;ServerName&lt;/span&gt; example.com
    &lt;span class="nc"&gt;ServerAlias&lt;/span&gt; www.example.com
    &lt;span class="nc"&gt;Redirect&lt;/span&gt; &lt;span class="ss"&gt;permanent&lt;/span&gt; / https://example.com/
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nl"&gt;VirtualHost&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing: Verify You Did It Right
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Quick command line test:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl s_client &lt;span class="nt"&gt;-connect&lt;/span&gt; example.com:443 &lt;span class="nt"&gt;-tls1_3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Protocol: TLSv1.3&lt;/code&gt; (or &lt;code&gt;TLSv1.2&lt;/code&gt; if 1.3 isn't available)&lt;/li&gt;
&lt;li&gt;Cipher suite information (should show AES-GCM or ChaCha20-Poly1305)&lt;/li&gt;
&lt;li&gt;Certificate info (expiry date, subject)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Web-based test:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Go to &lt;strong&gt;SSL Labs by Qualys&lt;/strong&gt; (&lt;a href="https://www.ssllabs.com/ssltest/" rel="noopener noreferrer"&gt;https://www.ssllabs.com/ssltest/&lt;/a&gt;). Enter your domain. It'll scan your server and grade it. A grade is a rough measure. A's are good. C's and below means you've got problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Browser devtools:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Open your site in a browser, hit F12, go to Security tab. It'll show certificate info and any TLS warnings.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Common Mistakes
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mixing HTTP and HTTPS:&lt;/strong&gt; You have HTTPS on the main page but load CSS, JavaScript, or images over HTTP. Browsers block this (mixed content). Enable HSTS and keep everything HTTPS.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Not renewing certificates:&lt;/strong&gt; The certificate expires, users see a warning, you lose traffic. Automate renewal.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Using self-signed certificates in production:&lt;/strong&gt; They don't prove anything. Use Let's Encrypt.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Setting HSTS too aggressively:&lt;/strong&gt; If you enable HSTS with preload and then need to rollback, you're stuck for months. Start with &lt;code&gt;max-age=3600&lt;/code&gt; (one hour), test it, then increase.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Supporting too many legacy ciphers:&lt;/strong&gt; I've seen configs that support RC4 and DES "just in case." They don't need to. Disable them.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Checklist
&lt;/h2&gt;

&lt;p&gt;Before you deploy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] TLS 1.2 and 1.3 only. No 1.0, 1.1, or SSL.&lt;/li&gt;
&lt;li&gt;[ ] Strong cipher suites with ECDHE or DHE (PFS).&lt;/li&gt;
&lt;li&gt;[ ] Certificate from a trusted CA (Let's Encrypt is fine).&lt;/li&gt;
&lt;li&gt;[ ] Certificate valid for your domain (no warnings).&lt;/li&gt;
&lt;li&gt;[ ] HSTS enabled (but test with short max-age first).&lt;/li&gt;
&lt;li&gt;[ ] Automated renewal (certbot for Let's Encrypt).&lt;/li&gt;
&lt;li&gt;[ ] Test with SSL Labs (aim for A or A+).&lt;/li&gt;
&lt;li&gt;[ ] HTTP redirects to HTTPS.&lt;/li&gt;
&lt;li&gt;[ ] No mixed content warnings.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's security in 2026. Not complicated. Just discipline.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Nils Eriksson&lt;/strong&gt; is a Stockholm-based security consultant. He writes about web security, bad practices, and the occasional existential crisis caused by production deployments.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>networking</category>
      <category>security</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Your Web Server Is Leaking: The Security Headers Most Developers Forget</title>
      <dc:creator>Nils Eriksson</dc:creator>
      <pubDate>Wed, 10 Jun 2026 13:04:35 +0000</pubDate>
      <link>https://dev.to/nilseriksson92/your-web-server-is-leaking-the-security-headers-most-developers-forget-hda</link>
      <guid>https://dev.to/nilseriksson92/your-web-server-is-leaking-the-security-headers-most-developers-forget-hda</guid>
      <description>&lt;h1&gt;
  
  
  Your Web Server Is Leaking: The Security Headers Most Developers Forget
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;Meta: Learn the critical HTTP security headers developers skip. CSP, HSTS, X-Frame-Options—what they do, why they matter, and how to implement them. Production checklist included.&lt;/p&gt;

&lt;p&gt;Keyword: HTTP security headers checklist, Content-Security-Policy, HSTS, web server security&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;I walked into a consulting call last week. Nice company, decent traffic, been running for three years. First thing I did: dumped their HTTP headers.&lt;/p&gt;

&lt;p&gt;Nothing. Zero security headers. Not even the basics.&lt;/p&gt;

&lt;p&gt;The CTO looked at me confused. "Wait, that matters?"&lt;/p&gt;

&lt;p&gt;Yeah. It matters. And judging by how often I see this, I'm not the only one cleaning up after this particular oversight. So let's fix it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Headers? Because Browsers Need Rules
&lt;/h2&gt;

&lt;p&gt;Here's the thing most developers don't get: your browser is inherently trusting. It'll run whatever JavaScript you give it. It'll load resources from wherever. It'll let parent pages mess with your iframe. None of that is bad in isolation—it's flexibility. But without guardrails, it's a security disaster.&lt;/p&gt;

&lt;p&gt;HTTP security headers are those guardrails. They're instructions you send back with every HTTP response that tell the browser: "Here are the rules for my site. Follow them."&lt;/p&gt;

&lt;p&gt;The beauty is they cost nothing. No performance hit. No infrastructure change. Just a few lines of config. And yet somehow, most sites don't have them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Content-Security-Policy: The Heavyweight Champion
&lt;/h2&gt;

&lt;p&gt;If you only implement one header, make it this one. CSP is the most powerful and most misunderstood security header out there.&lt;/p&gt;

&lt;p&gt;What it does: whitelists which sources the browser is allowed to load resources from. Inline scripts? Blocked by default. External stylesheets from random CDNs? Nope. Iframes pointing to sketchy domains? Denied.&lt;/p&gt;

&lt;p&gt;Why it matters: if an attacker injects malicious JavaScript into your page (through a vulnerable plugin, a compromised dependency, bad input sanitization—pick one), CSP stops them cold. They can't run their code. They can't exfiltrate data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The basic policy:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{random}'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break this down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;default-src 'self'&lt;/code&gt; — everything comes from your own origin by default&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;script-src 'self' 'nonce-{random}'&lt;/code&gt; — only your own scripts run, plus inline scripts with a nonce (more on that)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;style-src 'self' 'unsafe-inline'&lt;/code&gt; — this is the compromise line. Ideally, no inline styles, but CSS-in-JS and older frameworks need this&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;img-src 'self' data: https:&lt;/code&gt; — load images from your origin, data URIs, or HTTPS anywhere&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;connect-src 'self'&lt;/code&gt; — XHR/fetch only to your origin (blocks exfiltration)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;frame-ancestors 'none'&lt;/code&gt; — don't let anyone iframe your site&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;About nonces:&lt;/strong&gt; if you need inline scripts (and most modern SPAs do), use a nonce instead of &lt;code&gt;'unsafe-inline'&lt;/code&gt;. Generate a cryptographic random string, include it in your CSP header, and add it to your script tags:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;nonce=&lt;/span&gt;&lt;span class="s"&gt;"aj5k9s8d7f6"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;This runs because I have the nonce&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The nonce should be unique per request. Yeah, it's extra work, but it means you can allow specific inline scripts while blocking injected ones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pro tip:&lt;/strong&gt; start with &lt;code&gt;Content-Security-Policy-Report-Only&lt;/code&gt; header first. Same policy, but violations get logged instead of blocked. Let it run for a week, check your logs, fix the issues, then flip to the real header. I've seen too many sites go live with a broken CSP and break half their features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Strict-Transport-Security: Never Go Back to HTTP
&lt;/h2&gt;

&lt;p&gt;HSTS is simple: tell the browser "always use HTTPS for this domain, no exceptions."&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What this means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;max-age=31536000&lt;/code&gt; — remember this for one year (in seconds)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;includeSubDomains&lt;/code&gt; — apply this to ALL subdomains&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;preload&lt;/code&gt; — I want to be in the HSTS preload list (a hardcoded browser list of sites that always use HTTPS)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why it matters: if someone intercepts the initial HTTP request before they get redirected to HTTPS, an attacker can steal cookies and credentials. HSTS kills that vector—the browser refuses to connect via HTTP at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Word of warning:&lt;/strong&gt; &lt;code&gt;includeSubDomains&lt;/code&gt; is essential for security, but it's also a gun pointed at your foot. Every subdomain has to support HTTPS. If you have some ancient mail.yoursite.com still running on HTTP, HSTS will break it. I've seen companies shoot themselves in the foot here. Get your house in order first, then enable HSTS.&lt;/p&gt;

&lt;p&gt;Also: preload is optional but good. You're volunteering for a hardcoded list in Chrome, Firefox, Safari, and others. Once you're in, you can't get out quickly—it's essentially permanent. Only do this if you're 100% committed to HTTPS forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  X-Content-Type-Options: Stop MIME Type Sniffing
&lt;/h2&gt;

&lt;p&gt;One line. That's it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;X-Content-Type-Options: nosniff
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What it does: tells browsers "only run files as the MIME type I say they are." Without this, if you serve a file as &lt;code&gt;text/plain&lt;/code&gt; that's actually JavaScript, older browsers will sniff it and run it anyway.&lt;/p&gt;

&lt;p&gt;This is old-school security theatre by now—modern browsers are smarter. But it costs nothing, so add it.&lt;/p&gt;

&lt;h2&gt;
  
  
  X-Frame-Options (and frame-ancestors in CSP)
&lt;/h2&gt;

&lt;p&gt;Two ways to say the same thing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;X-Frame-Options: DENY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or via CSP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Content-Security-Policy: frame-ancestors 'none'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What it does: tells the browser "don't let anyone embed this page in an iframe." This blocks clickjacking attacks where an attacker overlays a transparent iframe over your login button, tricks users into clicking it, and steals their credentials.&lt;/p&gt;

&lt;p&gt;If you DO need to allow certain sites to iframe you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;X-Frame-Options: ALLOW-FROM https://trusted-partner.com
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But ALLOW-FROM is deprecated. Use CSP instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Content-Security-Policy: frame-ancestors 'self' https://trusted-partner.com
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CSP is the modern way. Drop both headers if you're CSP-only, but set both if you want to support older browsers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Referrer-Policy: Control What You Leak
&lt;/h2&gt;

&lt;p&gt;When you link to another site, browsers send the &lt;code&gt;Referer&lt;/code&gt; header (yes, it's a typo from 1994—we're stuck with it). That header includes your full URL, including query parameters.&lt;/p&gt;

&lt;p&gt;If your URLs contain sensitive data, that's leaking. Use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Referrer-Policy: strict-origin-when-cross-origin
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What this means: only send the origin (domain) when linking cross-site, send the full URL within your own site. It's the sensible default that doesn't break anything.&lt;/p&gt;

&lt;p&gt;Other options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;no-referrer&lt;/code&gt; — tell the browser nothing about where we're linking from (might break some analytics)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;same-origin&lt;/code&gt; — only send referrer for same-site requests&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;strict-origin&lt;/code&gt; — only send the origin, never the full path&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I use &lt;code&gt;strict-origin-when-cross-origin&lt;/code&gt; as the default. It's protective without being paranoid.&lt;/p&gt;

&lt;h2&gt;
  
  
  Permissions-Policy: Lock Down Browser APIs
&lt;/h2&gt;

&lt;p&gt;This one's newer, and most developers ignore it. Bad idea.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Permissions-Policy: microphone=(), camera=(), geolocation=(), payment=()
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What it does: tells the browser "my page doesn't need these APIs." If a third-party script somehow loads and tries to access your user's microphone, camera, or location, it gets denied.&lt;/p&gt;

&lt;p&gt;If you actually need one of these (video calls, location services), be specific:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Permissions-Policy: microphone=(self "https://trusted-service.com"), camera=(self), geolocation=()
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is especially important if you're running third-party ads or embeds. You're not denying the APIs outright; you're denying them to untrusted code.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Actually Implement This
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Nginx
&lt;/h3&gt;

&lt;p&gt;Drop this in your server block:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt; &lt;span class="s"&gt;ssl&lt;/span&gt; &lt;span class="s"&gt;http2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;yoursite.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# TLS config here...&lt;/span&gt;

    &lt;span class="c1"&gt;# Security Headers&lt;/span&gt;
    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Strict-Transport-Security&lt;/span&gt; &lt;span class="s"&gt;"max-age=31536000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="kn"&gt;includeSubDomains&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="kn"&gt;preload"&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;X-Content-Type-Options&lt;/span&gt; &lt;span class="s"&gt;"nosniff"&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;X-Frame-Options&lt;/span&gt; &lt;span class="s"&gt;"DENY"&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Referrer-Policy&lt;/span&gt; &lt;span class="s"&gt;"strict-origin-when-cross-origin"&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Permissions-Policy&lt;/span&gt; &lt;span class="s"&gt;"microphone=(),&lt;/span&gt; &lt;span class="s"&gt;camera=(),&lt;/span&gt; &lt;span class="s"&gt;geolocation=(),&lt;/span&gt; &lt;span class="s"&gt;payment=()"&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Content-Security-Policy&lt;/span&gt; &lt;span class="s"&gt;"default-src&lt;/span&gt; &lt;span class="s"&gt;'self'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="kn"&gt;script-src&lt;/span&gt; &lt;span class="s"&gt;'self'&lt;/span&gt; &lt;span class="s"&gt;'nonce-&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="kn"&gt;random&lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="kn"&gt;style-src&lt;/span&gt; &lt;span class="s"&gt;'self'&lt;/span&gt; &lt;span class="s"&gt;'unsafe-inline'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="kn"&gt;img-src&lt;/span&gt; &lt;span class="s"&gt;'self'&lt;/span&gt; &lt;span class="s"&gt;data:&lt;/span&gt; &lt;span class="s"&gt;https:&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="kn"&gt;font-src&lt;/span&gt; &lt;span class="s"&gt;'self'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="kn"&gt;connect-src&lt;/span&gt; &lt;span class="s"&gt;'self'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="kn"&gt;frame-ancestors&lt;/span&gt; &lt;span class="s"&gt;'none'"&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://backend&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the &lt;code&gt;always&lt;/code&gt; directive—that ensures headers get sent even on error responses (like 404).&lt;/p&gt;

&lt;h3&gt;
  
  
  Apache
&lt;/h3&gt;

&lt;p&gt;In your &lt;code&gt;.htaccess&lt;/code&gt; or VirtualHost:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nl"&gt;IfModule&lt;/span&gt;&lt;span class="sr"&gt; mod_headers.c&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="nc"&gt;Header&lt;/span&gt; &lt;span class="ss"&gt;always&lt;/span&gt; &lt;span class="ss"&gt;set&lt;/span&gt; Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
    &lt;span class="nc"&gt;Header&lt;/span&gt; &lt;span class="ss"&gt;always&lt;/span&gt; &lt;span class="ss"&gt;set&lt;/span&gt; X-Content-Type-Options "nosniff"
    &lt;span class="nc"&gt;Header&lt;/span&gt; &lt;span class="ss"&gt;always&lt;/span&gt; &lt;span class="ss"&gt;set&lt;/span&gt; X-Frame-Options "DENY"
    &lt;span class="nc"&gt;Header&lt;/span&gt; &lt;span class="ss"&gt;always&lt;/span&gt; &lt;span class="ss"&gt;set&lt;/span&gt; Referrer-Policy "strict-origin-when-cross-origin"
    &lt;span class="nc"&gt;Header&lt;/span&gt; &lt;span class="ss"&gt;always&lt;/span&gt; &lt;span class="ss"&gt;set&lt;/span&gt; Permissions-Policy "microphone=(), camera=(), geolocation=(), payment=()"
    &lt;span class="nc"&gt;Header&lt;/span&gt; &lt;span class="ss"&gt;always&lt;/span&gt; &lt;span class="ss"&gt;set&lt;/span&gt; Content-Security-Policy "default-src 'self'; script-src 'self' 'nonce-{random}'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'"
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nl"&gt;IfModule&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reload your server and you're done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing: Verify You Actually Did It
&lt;/h2&gt;

&lt;p&gt;Open your site in a browser, hit F12, go to the Network tab, click the main page request, and scroll down to Response Headers. You should see all the headers you just added.&lt;/p&gt;

&lt;p&gt;For a quick audit, hit &lt;strong&gt;securityheaders.com&lt;/strong&gt; and enter your domain. It'll grade you and tell you what you're missing. It's free and it'll give you a reality check.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Checklist
&lt;/h2&gt;

&lt;p&gt;Before you deploy, verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;code&gt;Strict-Transport-Security&lt;/code&gt; set with &lt;code&gt;includeSubDomains&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;X-Content-Type-Options: nosniff&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;X-Frame-Options: DENY&lt;/code&gt; or CSP &lt;code&gt;frame-ancestors 'none'&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;Content-Security-Policy&lt;/code&gt; with sensible defaults (start with Report-Only)&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;Referrer-Policy&lt;/code&gt; set to &lt;code&gt;strict-origin-when-cross-origin&lt;/code&gt; or stricter&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;Permissions-Policy&lt;/code&gt; locks down microphone, camera, geolocation, payment&lt;/li&gt;
&lt;li&gt;[ ] Test with securityheaders.com and browser devtools&lt;/li&gt;
&lt;li&gt;[ ] Monitor CSP violations if you're using Report-Only&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The End
&lt;/h2&gt;

&lt;p&gt;I walked out of that client call with a config file and a promise to check back in a week. Week later, they're running an A rating on securityheaders.com. Their users don't see the difference, but now an attacker would have a much harder time. That's the whole point.&lt;/p&gt;

&lt;p&gt;Security headers aren't sexy. They don't ship features. But they're the security equivalent of wearing a seatbelt—cheap, effective, and something you'll regret not doing if something goes wrong.&lt;/p&gt;

&lt;p&gt;Go add them. Your future self will thank you.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Nils Eriksson&lt;/strong&gt; is a Stockholm-based security consultant. He writes about web security, bad practices, and the occasional existential crisis caused by production deployments.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
