Skip to main content
โšก Calmops

HTTPS & Security Headers: TLS, CSP, HSTS and Secure Deployment

Securing web traffic is a foundational responsibility for web developers and DevOps engineers. HTTPS (TLS) protects data in transit while security headers such as Content Security Policy (CSP) and HTTP Strict Transport Security (HSTS) harden browser behavior and reduce attack surface. This guide explains the protocols, shows practical header configurations, and gives deployment advice you can apply today.


Overview

  • SSL vs TLS: TLS is the modern protocol; “SSL” is legacy naming
  • TLS handshake basics: key exchange, authentication, and symmetric encryption
  • CSP: reduce XSS risk by restricting sources for scripts, styles, frames
  • HSTS: force browsers to use HTTPS only
  • Practical deployment: certificate issuance/renewal, secure server configs, testing

SSL/TLS Essentials

What TLS Does

Transport Layer Security (TLS) encrypts HTTP traffic, preventing eavesdropping and tampering. A TLS connection provides:

  • Confidentiality (encryption)
  • Integrity (detect tampering)
  • Authentication (server identity via certificate)

Note: People still say “SSL/TLS” but TLS 1.2 and TLS 1.3 are the recommended protocolsโ€”disable SSLv3/TLS < 1.2.

How the TLS Handshake Works (high level)

  1. Client Hello: the client sends supported protocol versions, cipher suites, and a random value.
  2. Server Hello: server picks protocol version and cipher suite and sends its certificate chain.
  3. Key Exchange: using ECDHE or similar, both parties derive a shared secret.
  4. Verification and Finished messages: both sides verify the handshake and begin encrypted communication.

TLS 1.3 simplifies and speeds up this process (fewer roundtrips, mandatory forward secrecy).

Certificates and Trust

A TLS certificate includes a public key and identity info (subject). Browsers trust certificates issued by recognized Certificate Authorities (CAs). Common management steps:

  • Generate a private key and CSR (certificate signing request)
  • Have a CA issue the certificate (Let’s Encrypt, commercial CAs)
  • Install cert and key on your server
  • Renew before expiry (Let’s Encrypt: 90 days)

Inspect a certificate with openssl:

openssl s_client -connect example.com:443 -servername example.com \
  | openssl x509 -noout -text

Key Concepts and Best Practices

  • Use TLS 1.3 where possible; fall back to TLS 1.2 only if necessary
  • Prefer ECDHE key exchange for Perfect Forward Secrecy (PFS)
  • Use 2048+ RSA keys or ECDSA P-256 keys
  • Enable OCSP stapling to speed up revocation checks
  • Disable old ciphers (RC4, DES, 3DES, export ciphers)
  • Regularly test configs with SSL Labs: https://www.ssllabs.com/ssltest/

Example nginx TLS snippet (minimal, secure):

ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off; # TLS1.3 chooses secure ciphers
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;

Content Security Policy (CSP)

CSP is a browser mechanism that restricts what resources a page may load and execute, greatly reducing the impact of cross-site scripting (XSS) vulnerabilities.

CSP Basics

Set CSP with the Content-Security-Policy header. The policy lists directives that control resource types, e.g. default-src, script-src, style-src, img-src, frame-ancestors.

A conservative example:

Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; object-src 'none'; base-uri 'self'; frame-ancestors 'none';
  • default-src 'self' allows resources only from the same origin
  • script-src allows scripts from the site’s origin and a trusted CDN
  • object-src 'none' disables plugins
  • frame-ancestors 'none' prevents framing (clickjacking)

Inline scripts/styles are frequent causes of weakness. Instead of unsafe-inline, use nonces or hashes.

Server-side: generate a random nonce per response and include it in script tags and the CSP header.

Content-Security-Policy: script-src 'self' 'nonce-<RANDOM_NONCE>' https://cdn.example.com;

HTML:

<script nonce="<RANDOM_NONCE>">console.log('allowed inline script');</script>

Hashes are useful for static inline scripts (sha256-abc…). Nonces are better for dynamic pages.

Reporting and Iteration

Use Content-Security-Policy-Report-Only during rollout to collect violations without blocking. Provide a report-uri or report-to endpoint to gather reports.

Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report-endpoint

Common Pitfalls

  • unsafe-inline and unsafe-eval negate much of CSP’s protectionโ€”avoid them
  • Overly broad host wildcards (e.g., *.example.com) may include attacker-controlled subdomains in misconfigured DNS
  • Forgetting to include frame-ancestors allows clickjacking

HTTP Strict Transport Security (HSTS)

HSTS tells browsers to only use HTTPS when contacting your site for a specified period.

Header and Options

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
  • max-age is secondsโ€”31536000 equals one year
  • includeSubDomains applies HSTS to all subdomains
  • preload indicates intent to submit to the browser preload list (see https://hstspreload.org)

Deployment Notes

  1. Start with max-age=3600 (1 hour) and no includeSubDomains during testing
  2. Once stable, increase max-age to one year and add includeSubDomains if you control all subdomains
  3. Only add preload after careful testingโ€”being preloaded means removal requires coordination with browsers

Nginx example

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

Secure Deployment Practices

Certificate Management

  • Use Let’s Encrypt for automation or a commercial CA for extended validation
  • Automate renewals (certbot, acme.sh, or platform-managed certs)
  • Monitor expiry and set alerts
  • Use separate keys for dev/staging and production
  • For high-value keys, consider storing private keys in an HSM or cloud KMS

Example: issue and auto-renew with Certbot (nginx):

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
# Certbot installs renewal cron; test with:
sudo certbot renew --dry-run

Server Configuration Checklist

  • Redirect all HTTP to HTTPS (301)
  • Add HSTS after testing
  • Enable strong TLS protocols and ciphers
  • Enable OCSP stapling
  • Serve cookies with Secure; HttpOnly; SameSite=Strict where appropriate
  • Disable weak TLS versions and ciphers
  • Keep server software and OpenSSL updated

HTTP redirect example (nginx):

server {
  listen 80;
  server_name example.com www.example.com;
  return 301 https://$host$request_uri;
}

Extra Security Headers

Add these headers in addition to CSP and HSTS:

add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Permissions-Policy "geolocation=(), microphone=()" always; # formerly Feature-Policy
add_header X-XSS-Protection "0" always; # deprecated but some still set it

Testing & Validation

Common Pitfalls

  • Applying HSTS before HTTPS redirects are stable (locks users out if misconfigured)
  • Using wide CSPs (default-src *) that defeat the purpose
  • Forgetting to renew certificates or relying on manual renewals
  • Exposing private keys in version controlโ€”never commit private keys

Quick Reference: Example Express.js hardening

const express = require('express');
const helmet = require('helmet');
const app = express();

// Helmet sets many headers (CSP must be configured carefully)
app.use(helmet());

// HSTS (enable after verifying HTTPS)
app.use(helmet.hsts({
  maxAge: 31536000,
  includeSubDomains: true,
  preload: true
}));

// CSP with nonce (example middleware sets res.locals.cspNonce)
app.use((req, res, next) => {
  const nonce = crypto.randomBytes(16).toString('base64');
  res.locals.cspNonce = nonce;
  res.setHeader("Content-Security-Policy",
    `default-src 'self'; script-src 'self' 'nonce-${nonce}' https://cdn.example.com;`);
  next();
});

app.get('/', (req, res) => {
  res.send(`<script nonce="${res.locals.cspNonce}">console.log('hello')</script>`);
});

Conclusion

HTTPS and security headers are essential, practical measures with immediate impact:

  • Use TLS 1.3 and prefer ECDHE for forward secrecy
  • Automate certificate issuance and renewal (Let’s Encrypt + certbot)
  • Use CSP (with nonces or hashes) to mitigate XSS
  • Gradually deploy HSTS and consider preload only when ready
  • Harden servers, monitor certificates, and test with SSL Labs and Mozilla Observatory

These controls significantly reduce common attack vectors and are well worth the engineering effort.


Further Reading

Comments