Easier to read with cream paper, a tint, more contrast or larger text? Choose your colours and text, once, for every page.

Free lesson

A reverse proxy in front of an app

After this lesson: I can set up nginx as a reverse proxy in front of an app.

55 min lesson13 commandsWeb server hardening lab: fix a weak nginx configuration and check it with curl

One front door for three apps

A reverse proxy is a server that stands in front of an app. It takes each visitor's request and passes it on. It gives several apps one front door, one log and one place to protect.

A company runs a shop, a blog and a staff tool. Each is a small program on its own port, and none should face the internet by itself. This lesson builds the front door.

By the end of this lesson

Note

I can set up nginx as a reverse proxy in front of an app.

Key words

WordWhat it meansExample
Reverse proxya server that passes requests on to the app behind it
Upstreamthe app a proxy passes requests to
Load balancingspreading requests over several servers
502the status that says the proxy got no answer from the appBad Gateway
X-Forwarded-Fora header that carries the address of the real visitor
Reloadto make a running server read its settings again

The cells students fill in are left blank in this preview.

A reverse proxy answers for the app

A visitor talks to the reverse proxy. The proxy asks the app, the upstream, and hands the answer back. The visitor never meets the app.

Here the app is made out of nginx itself, on 127.0.0.1 port 9000, which only this machine can reach. The proxy is on port 8080. Each takes one file.

Write the app on port 9000

Command
docker exec web sh -c "printf '%s\n' 'server {' '  listen 127.0.0.1:9000;' '  location / { return 200 \"app server: host=\$host forwarded=\$http_x_forwarded_for from=\$remote_addr\n\"; }' '}' > /etc/nginx/conf.d/app.conf"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Write the proxy on port 8080

Command
docker exec web sh -c "printf '%s\n' 'server {' '  listen 8080;' '  location / {' '    proxy_pass http://127.0.0.1:9000;' '    proxy_set_header Host \$host;' '    proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;' '  }' '}' > /etc/nginx/conf.d/proxy.conf"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Test the settings before using them

Command
docker exec web nginx -t

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Reload nginx so it reads the new files

Command
docker exec web nginx -s reload

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Ask through the proxy

Command
curl -s http://web:8080/

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Ask the app directly, not through the proxy

Command
curl -sS -m 2 http://web:9000/; echo "exit code $?"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Headers tell the app who is asking

The app saw the proxy as the sender. To learn who really asked, the proxy writes the visitor's address into the header X-Forwarded-For.

It also passes on the name the visitor typed, in the Host header. Without that line, nginx sends the name of the upstream instead.

Send a request with a false sender

Command
curl -s -H 'X-Forwarded-For: 203.0.113.9' http://web:8080/

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

A common mistake

Warning

Many people think the first address in X-Forwarded-For is always the real visitor. A visitor can write that header themselves. Trust only the address your own proxy added, which is the last one in the list.

Worked example: what the proxy passes on

Line in proxy.confWhat the app learns
proxy_set_header Host $host;The name the visitor typed, here web
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;The visitor's address added to the end of the list
no Host lineThe name of the upstream, such as pool

The cells students fill in are left blank in this preview.

A dead app gives a 502

If the upstream does not answer, the proxy cannot fetch the page. It answers 502, Bad Gateway: the proxy is fine, the app behind it is not.

Two apps behind one name are load balancing. nginx hands them requests in turn, so one can fail and the other still answers.

Point the proxy at a dead port

Command
docker exec web sh -c "printf '%s\n' 'server {' '  listen 8080;' '  location / { proxy_pass http://127.0.0.1:9009; }' '}' > /etc/nginx/conf.d/proxy.conf && nginx -t && nginx -s reload"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Ask through the proxy and read the status

Command
curl -s -i http://web:8080/

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Read what nginx wrote about the failure

Command
docker logs web 2>&1 | grep -F "Connection refused" | tail -n 1

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Put two apps behind one name

Command
docker exec web sh -c "printf '%s\n' 'upstream pool {' '  zone pool 64k;' '  server 127.0.0.1:9000;' '  server 127.0.0.1:9001;' '}' 'server {' '  listen 127.0.0.1:9001;' '  location / { return 200 \"second app server\n\"; }' '}' > /etc/nginx/conf.d/pool.conf && sed -i 's#127.0.0.1:9009#pool#' /etc/nginx/conf.d/proxy.conf && nginx -t && nginx -s reload"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Ask four times and watch the turns

Command
for i in 1 2 3 4; do curl -s http://web:8080/; done

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Remove the three files and reload

Command
docker exec web sh -c "rm -f /etc/nginx/conf.d/app.conf /etc/nginx/conf.d/proxy.conf /etc/nginx/conf.d/pool.conf; nginx -s reload"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Going further: the answer that said host=pool

Look back at the four answers. One kind of answer says host=pool and not host=web.

Which row of the header table explains it? Write the one line you would add to the proxy's location block to make the app say host=web again.