Securitybeginner

HTTPS & SSL/TLS Basics — Securing Web Communication

Understand how HTTPS, SSL, and TLS protect data in transit. Learn the TLS handshake, certificate types, HSTS headers, and how to avoid mixed content issues.

12 min read·Published Apr 19, 2026
securityhttpsssltlsencryption

Why HTTPS Matters

HTTP sends data in plain text. Anyone between the client and server — ISPs, public WiFi operators, network attackers — can read, modify, or inject content into the communication. HTTPS wraps HTTP inside TLS (Transport Layer Security), providing three guarantees:

+---------------------+---------------------------------------------------+
| Guarantee           | What It Prevents                                  |
+---------------------+---------------------------------------------------+
| Confidentiality     | Eavesdropping — no one can read the data          |
| Integrity           | Tampering — no one can modify the data in transit |
| Authentication      | Impersonation — proves the server is who it claims|
+---------------------+---------------------------------------------------+

Without HTTPS:

 Client                 Attacker (MITM)              Server
 ------                 ---------------              ------
    |                        |                         |
    |-- GET /login --------->|                         |
    |                        |-- Reads credentials --->|
    |                        |<-- Reads response ------|
    |<-- Modified response --|                         |
    |   (injected malware)   |                         |

With HTTPS:

 Client                 Attacker (MITM)              Server
 ------                 ---------------              ------
    |                        |                         |
    |===== Encrypted TLS tunnel ======================|
    |                        |                         |
    |-- GET /login --------->| [Cannot read]           |
    |                        |                         |
    |<-- Response -----------| [Cannot modify]         |

HTTPS is not optional. Modern browsers mark HTTP sites as "Not Secure." Google uses HTTPS as a ranking signal. Features like service workers, geolocation, camera access, and HTTP/2 require HTTPS. Cookies with Secure and SameSite attributes only work over HTTPS.

SSL vs TLS: Terminology

SSL (Secure Sockets Layer) is the predecessor of TLS. SSL 1.0, 2.0, and 3.0 are all deprecated and insecure. TLS replaced SSL, and the current versions are:

+----------+--------+---------------------------------------------+
| Protocol | Year   | Status                                      |
+----------+--------+---------------------------------------------+
| SSL 1.0  | —      | Never released                              |
| SSL 2.0  | 1995   | Deprecated, insecure                        |
| SSL 3.0  | 1996   | Deprecated, insecure (POODLE attack)        |
| TLS 1.0  | 1999   | Deprecated since 2020                       |
| TLS 1.1  | 2006   | Deprecated since 2020                       |
| TLS 1.2  | 2008   | Widely used, still secure                   |
| TLS 1.3  | 2018   | Current standard, recommended               |
+----------+--------+---------------------------------------------+

When people say "SSL certificate," they almost always mean a TLS certificate. The term "SSL" persists out of habit.

How TLS Works: The Handshake

Before any application data is exchanged, the client and server perform a TLS handshake to establish a secure connection. Here is the TLS 1.2 handshake:

 Client                                              Server
 ------                                              ------
    |                                                   |
    |-- ClientHello ---------------------------------->|
    |   - Supported TLS versions                       |
    |   - Supported cipher suites                      |
    |   - Client random (32 bytes)                     |
    |                                                   |
    |<-- ServerHello ----------------------------------|
    |   - Selected TLS version                         |
    |   - Selected cipher suite                        |
    |   - Server random (32 bytes)                     |
    |                                                   |
    |<-- Certificate ----------------------------------|
    |   - Server's X.509 certificate                   |
    |   - Certificate chain                            |
    |                                                   |
    |<-- ServerHelloDone ------------------------------|
    |                                                   |
    |-- [Client verifies certificate]                  |
    |   - Is it signed by a trusted CA?                |
    |   - Is it expired?                               |
    |   - Does the domain match?                       |
    |                                                   |
    |-- ClientKeyExchange ---------------------------->|
    |   - Pre-master secret (encrypted with            |
    |     server's public key)                         |
    |                                                   |
    |-- [Both derive session keys from                 |
    |    client random + server random +               |
    |    pre-master secret]                            |
    |                                                   |
    |-- ChangeCipherSpec ------------------------------>|
    |-- Finished (encrypted) ------------------------->|
    |                                                   |
    |<-- ChangeCipherSpec -----------------------------|
    |<-- Finished (encrypted) -------------------------|
    |                                                   |
    |==== Secure connection established ===============|
    |==== Application data (HTTP) encrypted ===========|

TLS 1.3 simplifies this to a single round trip:

 Client                                              Server
 ------                                              ------
    |                                                   |
    |-- ClientHello + KeyShare ----------------------->|
    |   - Supported versions, cipher suites            |
    |   - Key share (Diffie-Hellman public key)        |
    |                                                   |
    |<-- ServerHello + KeyShare + Certificate ---------|
    |<-- Finished ------------------------------------|
    |                                                   |
    |-- Finished ------------------------------------->|
    |                                                   |
    |==== Secure connection (1 round trip) ============|

TLS 1.3 improvements:

  • Fewer round trips (1-RTT, or 0-RTT for resumed connections)
  • Removed insecure cipher suites (RC4, DES, 3DES, static RSA)
  • Mandatory forward secrecy (ephemeral Diffie-Hellman)
  • Encrypted more of the handshake (server certificate is encrypted)

Symmetric vs Asymmetric Encryption in TLS

TLS uses both types of encryption:

+-------------------+----------------------------+----------------------------+
|                   | Asymmetric (Public Key)    | Symmetric                  |
+-------------------+----------------------------+----------------------------+
| Keys              | Public key + private key   | Single shared key          |
| Speed             | Slow                       | Fast                       |
| Used for          | Key exchange, identity     | Bulk data encryption       |
| Algorithms        | RSA, ECDH                  | AES-256-GCM, ChaCha20     |
| TLS usage         | Handshake phase            | Data transfer phase        |
+-------------------+----------------------------+----------------------------+

The handshake uses asymmetric encryption to securely exchange a symmetric key. After that, all data is encrypted with the faster symmetric encryption.

 Handshake (asymmetric) --> Derive shared key --> Data transfer (symmetric)
                                                  (fast, efficient)

Certificate Types

TLS certificates prove the server's identity. A Certificate Authority (CA) validates the certificate applicant and signs the certificate.

+------------------+------------------+-------------------------------------------+
| Type             | Validation       | What CA Verifies                          |
+------------------+------------------+-------------------------------------------+
| DV (Domain       | Minutes          | Domain ownership only                     |
|   Validation)    |                  | (DNS or HTTP challenge)                   |
+------------------+------------------+-------------------------------------------+
| OV (Organization | Days             | Domain ownership + organization exists    |
|   Validation)    |                  | (legal entity, address)                   |
+------------------+------------------+-------------------------------------------+
| EV (Extended     | Weeks            | Domain + organization + legal status +    |
|   Validation)    |                  | physical address + authority of requester  |
+------------------+------------------+-------------------------------------------+

For most web applications, DV certificates are sufficient. They prove that you control the domain. Let's Encrypt provides free DV certificates.

Certificate Chain of Trust

Browsers trust a set of root CAs. The server's certificate is signed by an intermediate CA, which is signed by the root CA.

 Root CA (trusted by browser)
   |
   |-- Signs --> Intermediate CA
                   |
                   |-- Signs --> Your Server Certificate
                                   |
                                   |-- Proves: "example.com"
// Verify a certificate chain in Node.js (for understanding)
const tls = require('tls');
const https = require('https');

// Check certificate details
function inspectCertificate(hostname) {
  return new Promise((resolve, reject) => {
    const options = {
      hostname,
      port: 443,
      method: 'GET',
      rejectUnauthorized: true,
    };

    const req = https.request(options, (res) => {
      const cert = res.socket.getPeerCertificate(true);
      resolve({
        subject: cert.subject,
        issuer: cert.issuer,
        valid_from: cert.valid_from,
        valid_to: cert.valid_to,
        fingerprint: cert.fingerprint256,
        serialNumber: cert.serialNumber,
      });
    });

    req.on('error', reject);
    req.end();
  });
}

// Usage
inspectCertificate('example.com').then(console.log);

Let's Encrypt with Certbot

# Install certbot
sudo apt install certbot python3-certbot-nginx

# Obtain certificate (Nginx)
sudo certbot --nginx -d example.com -d www.example.com

# Auto-renewal (certbot installs a cron/systemd timer)
sudo certbot renew --dry-run

# Manual certificate generation (for other setups)
sudo certbot certonly --standalone -d example.com

Nginx configuration with TLS:

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

    # Certificate files from Let's Encrypt
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # 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;

    # OCSP stapling
    ssl_stapling on;
    ssl_stapling_verify on;

    # Security headers
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

    location / {
        proxy_pass http://localhost:3000;
    }
}

# Redirect HTTP to HTTPS
server {
    listen 80;
    server_name example.com;
    return 301 https://$server_name$request_uri;
}

HSTS (HTTP Strict Transport Security)

HSTS tells browsers to always use HTTPS for your domain, even if the user types http:// or clicks an HTTP link.

Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
+---------------------+---------------------------------------------------+
| Directive           | Meaning                                           |
+---------------------+---------------------------------------------------+
| max-age=63072000    | Remember for 2 years (in seconds)                 |
| includeSubDomains   | Apply to all subdomains too                       |
| preload             | Eligible for browser preload list                 |
+---------------------+---------------------------------------------------+

Without HSTS, the first request to your site could be HTTP (before the redirect to HTTPS). This creates a window for a MITM attack — the attacker intercepts the HTTP request and never lets the redirect happen (SSL stripping attack).

 Without HSTS:
 User types example.com -> Browser sends HTTP -> Attacker intercepts
                                                 (SSL stripping)

 With HSTS:
 User types example.com -> Browser KNOWS to use HTTPS -> Encrypted from start

Implementing HSTS

// Express with helmet
const helmet = require('helmet');

app.use(
  helmet.hsts({
    maxAge: 63072000,      // 2 years
    includeSubDomains: true,
    preload: true,
  })
);
// Next.js via next.config.js
module.exports = {
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          {
            key: 'Strict-Transport-Security',
            value: 'max-age=63072000; includeSubDomains; preload',
          },
        ],
      },
    ];
  },
};

HSTS Preload List

The HSTS preload list is hardcoded into browsers. Sites on this list use HTTPS from the very first visit — no initial HTTP request at all.

To be eligible:

  1. Serve a valid HTTPS certificate
  2. Redirect HTTP to HTTPS on the same host
  3. Serve the HSTS header with max-age >= 31536000, includeSubDomains, and preload
  4. Submit your domain at https://hstspreload.org

Warning: Adding your domain to the preload list is practically permanent. All subdomains must support HTTPS. Verify before submitting.

Mixed Content

Mixed content occurs when an HTTPS page loads resources (scripts, images, stylesheets) over HTTP. This undermines the security of the HTTPS page because the HTTP resources can be intercepted and modified.

+------------------------+---------------------------+----------------------------+
| Type                   | Examples                  | Browser Behavior           |
+------------------------+---------------------------+----------------------------+
| Active mixed content   | Scripts, iframes,         | Blocked by default         |
|                        | stylesheets, fetch        |                            |
+------------------------+---------------------------+----------------------------+
| Passive mixed content  | Images, video, audio      | Allowed with warning       |
|                        |                           | (being phased out)         |
+------------------------+---------------------------+----------------------------+

Detecting Mixed Content

// Check for mixed content in your page
function detectMixedContent() {
  const elements = document.querySelectorAll('[src], [href], [action]');
  const mixedContent = [];

  elements.forEach((el) => {
    const url = el.src || el.href || el.action;
    if (url && url.startsWith('http://')) {
      mixedContent.push({
        element: el.tagName,
        attribute: el.src ? 'src' : el.href ? 'href' : 'action',
        url: url,
      });
    }
  });

  if (mixedContent.length > 0) {
    console.warn('Mixed content detected:', mixedContent);
  }

  return mixedContent;
}

Fixing Mixed Content

<!-- BAD — HTTP resource on HTTPS page -->
<script src="http://cdn.example.com/lib.js"></script>
<img src="http://images.example.com/photo.jpg" />
<link href="http://fonts.example.com/font.css" rel="stylesheet" />

<!-- GOOD — Use HTTPS -->
<script src="https://cdn.example.com/lib.js"></script>
<img src="https://images.example.com/photo.jpg" />
<link href="https://fonts.example.com/font.css" rel="stylesheet" />

<!-- GOOD — Protocol-relative (auto-matches page protocol) -->
<!-- Note: HTTPS-only is preferred over protocol-relative -->
<script src="//cdn.example.com/lib.js"></script>

Use the upgrade-insecure-requests CSP directive to automatically upgrade HTTP resources to HTTPS:

Content-Security-Policy: upgrade-insecure-requests;
// Express
app.use((req, res, next) => {
  res.setHeader('Content-Security-Policy', 'upgrade-insecure-requests');
  next();
});

HTTPS in Development

You need HTTPS in development for testing features that require it (service workers, secure cookies, some APIs).

Self-Signed Certificate with mkcert

# Install mkcert
brew install mkcert   # macOS
# or
sudo apt install mkcert  # Ubuntu

# Create local CA (one-time)
mkcert -install

# Generate certificate for localhost
mkcert localhost 127.0.0.1 ::1

# Creates: localhost+2.pem and localhost+2-key.pem

Using the certificate in a Node.js server:

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

const app = express();

const server = https.createServer(
  {
    key: fs.readFileSync('./localhost+2-key.pem'),
    cert: fs.readFileSync('./localhost+2.pem'),
  },
  app
);

server.listen(3000, () => {
  console.log('HTTPS server running on https://localhost:3000');
});

Using with Next.js (custom server):

// server.js
const { createServer } = require('https');
const { parse } = require('url');
const next = require('next');
const fs = require('fs');

const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();

const httpsOptions = {
  key: fs.readFileSync('./localhost+2-key.pem'),
  cert: fs.readFileSync('./localhost+2.pem'),
};

app.prepare().then(() => {
  createServer(httpsOptions, (req, res) => {
    const parsedUrl = parse(req.url, true);
    handle(req, res, parsedUrl);
  }).listen(3000, () => {
    console.log('> Ready on https://localhost:3000');
  });
});

Testing TLS Configuration

Using OpenSSL

# Check certificate details
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -text

# Check supported TLS versions
openssl s_client -connect example.com:443 -tls1_2
openssl s_client -connect example.com:443 -tls1_3

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

Programmatic TLS Check

const https = require('https');

function checkTLS(hostname) {
  return new Promise((resolve, reject) => {
    const req = https.get(`https://${hostname}`, (res) => {
      const cert = res.socket.getPeerCertificate();
      const protocol = res.socket.getProtocol();
      const cipher = res.socket.getCipher();

      resolve({
        hostname,
        protocol,               // e.g., 'TLSv1.3'
        cipher: cipher.name,    // e.g., 'TLS_AES_256_GCM_SHA384'
        certSubject: cert.subject.CN,
        certIssuer: cert.issuer.O,
        validFrom: cert.valid_from,
        validTo: cert.valid_to,
        daysUntilExpiry: Math.floor(
          (new Date(cert.valid_to) - new Date()) / (1000 * 60 * 60 * 24)
        ),
      });
    });

    req.on('error', reject);
  });
}

checkTLS('example.com').then(console.log);

HTTPS Enforcement Checklist

+------+----------------------------------------------+----------+
| #    | Check                                        | Status   |
+------+----------------------------------------------+----------+
| 1    | TLS 1.2+ only (1.0/1.1 disabled)             | [ ]      |
| 2    | Strong cipher suites (AES-GCM, ChaCha20)     | [ ]      |
| 3    | HTTP redirects to HTTPS (301)                 | [ ]      |
| 4    | HSTS header set (max-age >= 1 year)           | [ ]      |
| 5    | HSTS includes subdomains                       | [ ]      |
| 6    | Certificate not expired                        | [ ]      |
| 7    | Certificate auto-renewal configured            | [ ]      |
| 8    | No mixed content on any page                   | [ ]      |
| 9    | Cookies set with Secure flag                   | [ ]      |
| 10   | OCSP stapling enabled                          | [ ]      |
| 11   | Forward secrecy enabled (ECDHE)                | [ ]      |
| 12   | SSL Labs score A or A+                         | [ ]      |
+------+----------------------------------------------+----------+

Summary

HTTPS is the foundation of web security. Every other security measure (cookies, CSP, CORS) assumes a secure transport layer.

Key takeaways:

  1. Always use HTTPS — No exceptions. Free certificates are available from Let's Encrypt.
  2. Use TLS 1.2+ — Disable TLS 1.0 and 1.1. Prefer TLS 1.3 where possible.
  3. Deploy HSTS — Prevent SSL stripping attacks. Use max-age=63072000; includeSubDomains; preload.
  4. Fix mixed content — One HTTP resource on an HTTPS page undermines the entire page's security.
  5. Automate renewal — Certificate expiration is the #1 cause of HTTPS outages.
  6. Test your configuration — Use SSL Labs (ssllabs.com/ssltest) to verify your TLS setup.

HTTPS protects data in transit. It does not protect against XSS, CSRF, SQL injection, or other application-layer attacks. It is necessary but not sufficient — layer it with the other security measures in this series.

Found this helpful?

Support devsofus — help us keep creating free dev guides.

Related Articles