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)
- Client Hello: the client sends supported protocol versions, cipher suites, and a random value.
- Server Hello: server picks protocol version and cipher suite and sends its certificate chain.
- Key Exchange: using ECDHE or similar, both parties derive a shared secret.
- 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 originscript-srcallows scripts from the site’s origin and a trusted CDNobject-src 'none'disables pluginsframe-ancestors 'none'prevents framing (clickjacking)
Nonces and Hashes (recommended over ‘unsafe-inline’)
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-inlineandunsafe-evalnegate 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-ancestorsallows 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-ageis secondsโ31536000equals one yearincludeSubDomainsapplies HSTS to all subdomainspreloadindicates intent to submit to the browser preload list (see https://hstspreload.org)
Deployment Notes
- Start with
max-age=3600(1 hour) and noincludeSubDomainsduring testing - Once stable, increase
max-ageto one year and addincludeSubDomainsif you control all subdomains - Only add
preloadafter 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=Strictwhere 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
- SSL Labs test: https://www.ssllabs.com/ssltest/
- Mozilla Observatory: https://observatory.mozilla.org/
- CSP reporting to see blocked resources
- Automated scans in CI to check for weak ciphers and expiring certs
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
- Mozilla TLS recommendations: https://ssl-config.mozilla.org/
- HSTS Preload: https://hstspreload.org/
- Content Security Policy (MDN): https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
- OWASP TLS Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Protection_Cheat_Sheet.html
- SSL Labs: https://www.ssllabs.com/
Comments