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
| Word | What it means | Example |
|---|---|---|
| Reverse proxy | a server that passes requests on to the app behind it | |
| Upstream | the app a proxy passes requests to | |
| Load balancing | spreading requests over several servers | |
| 502 | the status that says the proxy got no answer from the app | Bad Gateway |
| X-Forwarded-For | a header that carries the address of the real visitor | |
| Reload | to 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
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
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
docker exec web nginx -tShown 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
docker exec web nginx -s reloadShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Ask through the proxy
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
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
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.conf | What 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 line | The 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
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
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
docker logs web 2>&1 | grep -F "Connection refused" | tail -n 1Shown 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
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
for i in 1 2 3 4; do curl -s http://web:8080/; doneShown 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
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.