TechLead
Lesson 6 of 9
5 min read
Web Security

HTTPS & TLS Security

Implement secure transport with HTTPS, configure TLS properly, and use HSTS for maximum protection.

HTTPS & Transport Security

HTTPS encrypts data in transit between client and server, preventing eavesdropping and tampering. It's no longer optional—it's required for any modern web application.

Why HTTPS Matters

  • Encryption - Data can't be read by attackers
  • Integrity - Data can't be modified in transit
  • Authentication - Users know they're on the real site
  • SEO - Google ranks HTTPS sites higher
  • Features - Required for HTTP/2, service workers, etc.

Setting Up HTTPS with Let's Encrypt

# Using Certbot for automatic certificate management
sudo apt install certbot python3-certbot-nginx

# Get certificate
sudo certbot --nginx -d example.com -d www.example.com

# Auto-renewal is set up automatically
# Test with:
sudo certbot renew --dry-run

Node.js HTTPS Server

const https = require('https');
const fs = require('fs');
const express = require('express');

const app = express();

const options = {
  key: fs.readFileSync('/path/to/privkey.pem'),
  cert: fs.readFileSync('/path/to/fullchain.pem'),
  // Modern TLS configuration
  minVersion: 'TLSv1.2',
  ciphers: [
    'ECDHE-ECDSA-AES128-GCM-SHA256',
    'ECDHE-RSA-AES128-GCM-SHA256',
    'ECDHE-ECDSA-AES256-GCM-SHA384',
    'ECDHE-RSA-AES256-GCM-SHA384',
  ].join(':'),
};

https.createServer(options, app).listen(443);

// Redirect HTTP to HTTPS
const http = require('http');
http.createServer((req, res) => {
  res.writeHead(301, { Location: `https://${req.headers.host}${req.url}` });
  res.end();
}).listen(80);

HSTS (HTTP Strict Transport Security)

// Force HTTPS for all future requests
app.use((req, res, next) => {
  res.setHeader(
    'Strict-Transport-Security',
    'max-age=31536000; includeSubDomains; preload'
  );
  next();
});

// Or with Helmet
const helmet = require('helmet');
app.use(helmet.hsts({
  maxAge: 31536000,        // 1 year
  includeSubDomains: true, // Apply to all subdomains
  preload: true,           // Allow HSTS preloading
}));

Next.js HTTPS Configuration

// next.config.js
module.exports = {
  async headers() {
    return [
      {
        source: '/:path*',
        headers: [
          {
            key: 'Strict-Transport-Security',
            value: 'max-age=31536000; includeSubDomains; preload',
          },
        ],
      },
    ];
  },

  // Force HTTPS in production
  async redirects() {
    return process.env.NODE_ENV === 'production'
      ? [
          {
            source: '/:path*',
            has: [{ type: 'header', key: 'x-forwarded-proto', value: 'http' }],
            destination: 'https://example.com/:path*',
            permanent: true,
          },
        ]
      : [];
  },
};

Certificate Pinning

// Public Key Pinning (for mobile apps or critical applications)
const https = require('https');
const crypto = require('crypto');

const expectedFingerprint = 'SHA256:XXXX...';

const options = {
  hostname: 'api.example.com',
  port: 443,
  checkServerIdentity: (host, cert) => {
    const fingerprint = crypto
      .createHash('sha256')
      .update(cert.raw)
      .digest('base64');

    if (`SHA256:${fingerprint}` !== expectedFingerprint) {
      throw new Error('Certificate fingerprint mismatch!');
    }
  },
};

https.get(options, (res) => {
  // Proceed with request
});

Nginx HTTPS Configuration

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

server {
    listen 443 ssl http2;
    server_name example.com www.example.com;

    # Certificate files
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # Modern TLS configuration
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;

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

    # OCSP Stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;

    # Session configuration
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:50m;
    ssl_session_tickets off;
}

Testing TLS Configuration

# Test with SSL Labs
# Visit: https://www.ssllabs.com/ssltest/

# Test with OpenSSL
openssl s_client -connect example.com:443 -servername example.com

# Check certificate expiration
echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -dates

# Test specific TLS version
openssl s_client -connect example.com:443 -tls1_2
openssl s_client -connect example.com:443 -tls1_3

Mixed Content Issues

<!-- BAD: Loading HTTP resources on HTTPS page -->
<img src="http://example.com/image.jpg">
<script src="http://cdn.example.com/script.js"></script>

<!-- GOOD: Use HTTPS or protocol-relative URLs -->
<img src="https://example.com/image.jpg">
<img src="//example.com/image.jpg">

<!-- Best: Use CSP to block mixed content -->
<meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests">

HTTPS Checklist

  • Use TLS 1.2 or 1.3 only (disable older versions)
  • Use strong cipher suites
  • Enable HSTS with long max-age
  • Redirect all HTTP to HTTPS
  • Use secure cookies (Secure flag)
  • Fix mixed content issues
  • Set up automatic certificate renewal
  • Enable OCSP stapling
  • Test with SSL Labs (aim for A+ rating)

Continue Learning