# Install NGINX with Docker: Web Server, Reverse Proxy and HTTPS

> Run NGINX in a container to serve a static site, proxy requests to an application, balance load between replicas and add TLS, with the configuration explained.

- Source: https://www.itwonderlab.com/install-nginx-docker/
- Published: 2026-09-28
- Updated: 2026-09-28
- Author: Javier Ruiz Jiménez (https://www.javierruizjimenez.com/)
- Site: IT Wonder Lab (https://www.itwonderlab.com/)

---

## What is NGINX

[NGINX](https://www.itwonderlab.com/nginx/) is a fast web server that is also used as a **reverse proxy** and **load balancer**. A reverse proxy receives requests from the internet and forwards them to your applications, so you can add TLS, caching and routing in one place. This tutorial runs it in [Docker](https://www.itwonderlab.com/docker/) in three roles.

## Serve a static site

Create a folder with a page:

```bash
mkdir -p site && echo "<h1>Hello from NGINX</h1>" > site/index.html
docker run -d --name web -p 8080:80 -v "$PWD/site:/usr/share/nginx/html:ro" nginx:stable
```

Open `http://localhost:8080`. The image serves `/usr/share/nginx/html` and reads its configuration from `/etc/nginx/conf.d/`.

## Use your own configuration

```nginx title="nginx.conf"
server {
    listen 80;
    server_name _;

    root /usr/share/nginx/html;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }

    location ~* \.(css|js|png|jpg|svg)$ {
        expires 7d;
        add_header Cache-Control "public";
    }
}
```

Mount it as a server block and test the syntax before reloading:

```bash
docker run -d --name web -p 8080:80 \
  -v "$PWD/site:/usr/share/nginx/html:ro" \
  -v "$PWD/nginx.conf:/etc/nginx/conf.d/default.conf:ro" nginx:stable
docker exec web nginx -t
docker exec web nginx -s reload
```

## Reverse proxy and load balancing

Put NGINX in front of two replicas of an application. The `upstream` block lists them and NGINX distributes the requests round robin.

```nginx title="proxy.conf"
upstream app {
    server app1:3000;
    server app2:3000;
}

server {
    listen 80;

    location / {
        proxy_pass http://app;
        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;
    }
}
```

```yaml title="compose.yaml"
services:
  proxy:
    image: nginx:stable
    ports:
      - "80:80"
    volumes:
      - ./proxy.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on: [app1, app2]
  app1:
    image: myapp:1.0
  app2:
    image: myapp:1.0
```

Only the proxy publishes a port. The applications are reachable just through the Compose network (see [Docker networking](https://www.itwonderlab.com/docker-networking/)). On AWS the equivalent managed service is the [Elastic Load Balancing](https://www.itwonderlab.com/aws-elastic-load-balancing/).

## HTTPS

Add a second server block with your certificate and key mounted into the container:

```nginx title="tls.conf"
server {
    listen 443 ssl;
    http2 on;
    server_name example.com;

    ssl_certificate     /etc/nginx/certs/fullchain.pem;
    ssl_certificate_key /etc/nginx/certs/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;

    location / {
        proxy_pass http://app;
    }
}

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

For free certificates use Let's Encrypt with `certbot`, or an [AWS Certificate Manager](https://www.itwonderlab.com/aws-acm/) certificate on a load balancer. For local development create a self-signed one with `openssl req -x509 -nodes -newkey rsa:2048 -days 365 -keyout privkey.pem -out fullchain.pem -subj "/CN=localhost"`.

## Logs and troubleshooting

```bash
docker logs -f web          # access and error logs go to stdout and stderr
docker exec web nginx -T    # print the whole effective configuration
```

A `502 Bad Gateway` means NGINX reached no healthy upstream: check the names in `upstream` and that the application listens on `0.0.0.0`, not `127.0.0.1`.

## Alternatives

In Kubernetes, ingress controllers such as [Traefik](https://www.itwonderlab.com/install-traefik-docker/) or the NGINX ingress controller play this role.
