> ## Documentation Index
> Fetch the complete documentation index at: https://notes.kodekloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Demo HTTPS

> Guide to securing web traffic with NGINX by redirecting HTTP to HTTPS, generating test certificates, terminating TLS at the proxy, and optionally proxying to HTTPS backend servers

Securing web traffic requires configuring NGINX to redirect HTTP to HTTPS and to serve TLS certificates for encrypted connections. This guide shows how to:

* Redirect all HTTP traffic to HTTPS using a 301 redirect.
* Generate locally trusted test certificates with mkcert (for local/testing).
* Terminate TLS at NGINX and optionally proxy traffic to backend Apache servers over HTTPS (end-to-end TLS).

![A network diagram showing users connecting through a network cloud to an NGINX reverse proxy (HTTPS), which forwards traffic to two Apache web servers listening on port 443.](https://mintcdn.com/kodekloud-c4ac6d9a/x1wBthvq78ZWHRHX/images/Nginx-for-Beginners/Security/Demo-HTTPS/nginx-https-reverse-proxy-apache-443.jpg?fit=max\&auto=format\&n=x1wBthvq78ZWHRHX\&q=85\&s=7dbba0b98a2afa9fa4f9420997c1eecf)

## Prerequisites and quick checklist

* NGINX installed on the front-end host.
* Apache (or another web server) on backend hosts if proxying.
* Root or sudo access to manage firewall, certificates, and NGINX config.
* Public DNS and IP for production certificate issuance; mkcert is only for development.

Ports to allow (front-end and backends):

| Host role | Required ports | Purpose |
| - | - | - |
| NGINX (front-end) | `22`, `80`, `443` | SSH, HTTP (redirect), HTTPS (TLS termination) |
| Backends (Apache) | `22`, `443` | SSH, HTTPS (when proxying to backends over TLS) |

## 1. Prepare firewall and confirm services

Make sure the NGINX host allows SSH and HTTP, then add HTTPS (`443/tcp`).

```shell theme={null}
root@nginx:~# ufw status
Status: active

To                         Action      From
--                         ------      ----
22/tcp                     ALLOW       Anywhere
80/tcp                     ALLOW       Anywhere
22/tcp (v6)                ALLOW       Anywhere (v6)
80/tcp (v6)                ALLOW       Anywhere (v6)
```

Open port 443 and verify:

```shell theme={null}
root@nginx:~# ufw allow 443/tcp
root@nginx:~# ufw status
Status: active

To                         Action      From
--                         ------      ----
22/tcp                     ALLOW       Anywhere
80/tcp                     ALLOW       Anywhere
443/tcp                    ALLOW       Anywhere
22/tcp (v6)                ALLOW       Anywhere (v6)
80/tcp (v6)                ALLOW       Anywhere (v6)
443/tcp (v6)               ALLOW       Anywhere (v6)
```

## 2. Enable the HTTPS site configuration

Remove the previous HTTP-only site and enable a new HTTPS site configuration.

```shell theme={null}
root@nginx:~# rm /etc/nginx/sites-enabled/example-http
root@nginx:~# ls -l /etc/nginx/sites-available
total 16
-rw-r--r-- 1 root root 419 Feb 11 04:05 example-http
-rw-r--r-- 1 root root 656 Feb 11 04:04 example-https
```

Open `/etc/nginx/sites-available/example-https` and ensure it contains two key server blocks:

* An HTTP server listening on port 80 that issues a `301` redirect to the HTTPS URL.
* An HTTPS server listening on `443 ssl` that references the certificate and key paths.

Example `example-https` configuration:

```nginx theme={null}
# Redirect HTTP to HTTPS
server {
    listen 80;

    server_name example.com;
    return 301 https://$host$request_uri;
}

# HTTPS server
server {
    listen 443 ssl;

    server_name example.com;

    ssl_certificate /etc/ssl/certs/example.com.pem;
    ssl_certificate_key /etc/ssl/certs/example.com-key.pem;

    root /var/www/html;

    # Add index.php to the list if you are using PHP
    index index.html index.htm index.nginx-debian.html;

    location / {
        # First attempt to serve request as file, then
        # as directory, then fall back to displaying a 404.
        try_files $uri $uri/ =404;
    }
}
```

## 3. Generate test certificates with mkcert (development)

If certificates referenced by the config do not exist yet, NGINX will fail when validating them after you enable the site. For development, use mkcert to create locally trusted certs. In production, use a trusted CA (e.g., Let's Encrypt via Certbot).

Create the certificate files under `/etc/ssl/certs`:

```shell theme={null}
root@nginx:~# cd /etc/ssl/certs
root@nginx:/etc/ssl/certs# mkcert example.com
Created a new local CA at "/root/.local/share/mkcert" ✅
Created a certificate valid for the following names
 - "example.com"
Certificate and key files saved at:
 - "/etc/ssl/certs/example.com.pem"
 - "/etc/ssl/certs/example.com-key.pem"
```

After creating the certificate and key at the paths referenced in the NGINX config, test the configuration and reload NGINX:

```shell theme={null}
root@nginx:/etc/nginx/sites-available# ln -s /etc/nginx/sites-available/example-https /etc/nginx/sites-enabled/
root@nginx:/etc/nginx/sites-available# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

root@nginx:/etc/nginx/sites-available# nginx -s reload
```

At this point, browsing to `https://example.com` will serve the site over HTTPS using the test certificate.

For production, obtain publicly trusted certificates via an ACME client like Certbot with [Let's Encrypt](https://letsencrypt.org). That requires public DNS and a reachable IP for your domain.

## 4. Reverse proxy to HTTPS backends (upstream over 443)

To keep encryption between NGINX and backend servers, configure an upstream that points to backend IPs on port 443 and use `proxy_pass https://...` from the front-end.

Full example with upstream and `proxy_pass`:

```nginx theme={null}
# Upstream configuration
upstream example {
    server 192.230.210.3:443;
    server 192.230.210.6:443;
}

# Redirect HTTP to HTTPS
server {
    listen 80;

    server_name example.com;
    return 301 https://$host$request_uri;
}

# TLS-terminating proxy (front-end)
server {
    listen 443 ssl;

    server_name example.com;

    ssl_certificate /etc/ssl/certs/example.com.pem;
    ssl_certificate_key /etc/ssl/certs/example.com-key.pem;

    root /var/www/html;

    # Add index.php to the list if you are using PHP
    index index.html index.htm index.nginx-debian.html;

    location / {
        proxy_pass https://example;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # If the backend uses a self-signed certificate, you may need
        # to disable verification (not recommended for production):
        # proxy_ssl_verify off;
    }
}
```

Key notes when proxying to HTTPS backends:

* `proxy_pass https://example;` instructs NGINX to use HTTPS for upstream connections.
* Forwarding proxy headers (Host, X-Real-IP, X-Forwarded-For, X-Forwarded-Proto) helps backends know the original request details.
* For upstreams using hostnames (not IP), enable SNI so NGINX sends the expected server name:

```nginx theme={null}
proxy_ssl_server_name on;
proxy_ssl_name <backend-hostname>;
```

* For self-signed backend certs, configure `proxy_ssl_trusted_certificate` and related directives for proper validation. Using `proxy_ssl_verify off` is discouraged in production.

## 5. Example backend (Apache) TLS configuration

Each Apache backend must accept TLS on port 443. Example Apache virtual host:

```apache theme={null}
<VirtualHost *:80>
    ServerAdmin webmaster@localhost
    ServerName example.com
    Redirect permanent / https://example.com/
</VirtualHost>

<VirtualHost *:443>
    SSLEngine on
    ServerName example.com
    DocumentRoot /var/www/html
    SSLCertificateFile /etc/ssl/certs/example.com.pem
    SSLCertificateKeyFile /etc/ssl/certs/example.com-key.pem

    CustomLog ${APACHE_LOG_DIR}/access.log combined
    ErrorLog ${APACHE_LOG_DIR}/error.log
</VirtualHost>
```

If the Apache backends redirect HTTP to HTTPS, direct requests to a backend will end up on HTTPS. When proxied through the front-end, NGINX connects to the backend over HTTPS as configured.

## 6. Verify backend load balancing and served responses

A simple way to confirm requests are distributed to each backend is to change the HTML on each node so it reveals the hostname or an identifier (e.g., NODE01, NODE02).

Example edit to `/var/www/html/index.html` on node01:

```html theme={null}
<!-- Menu -->
<nav id="menu">
    <h2>Menu</h2>
    <ul>
        <li><a href="index.html">Home NODE01</a></li>
        <li><a href="generic.html">Ipsum veroeros</a></li>
        ...
    </ul>
</nav>
```

And similar on node02 with “NODE02”. Hitting `https://example.com` multiple times should show different responses depending on backend selection.

## 7. Open ports on backends

Ensure backend servers allow HTTPS so NGINX can connect to them:

```shell theme={null}
root@node01:~# ufw status
Status: active

To                         Action      From
--                         ------      ----
22/tcp                     ALLOW       Anywhere
22/tcp (v6)                ALLOW       Anywhere (v6)

# Allow HTTPS
root@node01:~# ufw allow 443/tcp
root@node01:~# ufw status
Status: active

To                         Action      From
--                         ------      ----
22/tcp                     ALLOW       Anywhere
443/tcp                    ALLOW       Anywhere
22/tcp (v6)                ALLOW       Anywhere (v6)
443/tcp (v6)               ALLOW       Anywhere (v6)
```

Remember to open port `443` on any backend NGINX will proxy to — otherwise the front-end cannot establish HTTPS connections to them.

## 8. Final test: reload NGINX and browse

After updating configurations and placing certificates, test and reload NGINX:

```shell theme={null}
root@nginx:/etc/nginx/sites-enabled# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

root@nginx:/etc/nginx/sites-enabled# nginx -s reload
```

Now browse to `https://example.com`. NGINX will:

* Redirect HTTP to HTTPS using a `301`.
* Terminate TLS at the front-end (using your certificate).
* Optionally proxy requests to upstream Apache backends over HTTPS.

## Summary and best practices

* Always redirect port 80 to HTTPS using a `301` to encourage secure client connections.
* Use `mkcert` for local or development certificates. For production, use a publicly trusted CA like [Let's Encrypt](https://letsencrypt.org) or a commercial CA, typically via an ACME client such as [Certbot](https://certbot.eff.org).
* To proxy to backends over TLS, use `proxy_pass https://upstream;` and configure upstream targets on port `443`.
* Forward proxy headers so backends receive original request metadata.
* Manage certificate validation for upstream servers—prefer proper CA-signed certs or configure `proxy_ssl_trusted_certificate` rather than disabling verification.
* Ensure firewalls on both front-end and back-end hosts allow required ports (notably `443`).

Further reading and references:

* [NGINX documentation](https://nginx.org/en/docs/)
* [mkcert — simple local CA](https://mkcert.dev)
* [Let's Encrypt](https://letsencrypt.org)
* [Certbot (EFF)](https://certbot.eff.org)

> **[Watch Video](https://learn.kodekloud.com/user/courses/nginx-for-beginners/module/8905470e-b1ea-48ec-b0cd-711687ce7159/lesson/af1b832f-e266-40ac-b2f3-86945bc5805b)**

> **[Practice Lab](https://learn.kodekloud.com/user/courses/nginx-for-beginners/module/8905470e-b1ea-48ec-b0cd-711687ce7159/lesson/64f0226a-545c-4456-b704-e4ee931b410f)**


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.