---
title: "Reverse proxy là gì? Cách cấu hình Nginx và phân biệt với API gateway"
description: "Reverse proxy là gì, khác forward proxy và API gateway ra sao? Cấu hình reverse proxy Nginx với proxy_pass, upstream, X-Forwarded-For và cách xử lý lỗi 502."
canonical: "https://200lab.io/blog/reverse-proxy-la-gi"
published: "2026-11-02T10:38:23Z"
updated: "2026-11-02T10:38:23Z"
authors: ["Việt Trần"]
tags: ["Backend", "DevOps"]
reading_minutes: 12
access: "public"
---

Khi vừa đưa một app chạy ở cổng **3000** hay **8000** lên server, sớm muộn gì bạn cũng cần gắn domain có HTTPS hoặc thêm server thứ hai để gánh phụ. Đó là lúc bạn cần dựng một reverse proxy đứng chắn phía trước.

Blog đã có bài giải thích [proxy và forward proxy là gì](https://200lab.io/blog/proxy-la-gi), còn bài này đi sâu vào reverse proxy, phân biệt với API gateway và chạy thử file config Nginx thật.

## Reverse proxy là gì?

Reverse proxy là một server đứng trước một hoặc nhiều backend. Khi trình duyệt hoặc app của người dùng gửi request tới, reverse proxy nhận lấy rồi chuyển tiếp vào backend phù hợp ở phía sau.

Sau khi backend xử lý xong, reverse proxy nhận lại response và trả về cho client. Suốt quá trình này, client chỉ thấy địa chỉ của reverse proxy mà không biết địa chỉ thật của các backend bên trong.

## Forward proxy và reverse proxy khác nhau ở đâu

Forward proxy đứng phía client: máy tính trong mạng nội bộ đi ra Internet qua proxy, nên website đích chỉ thấy IP của con proxy đó. Ngược lại, reverse proxy đứng phía server: request từ Internet đi vào qua proxy, và client không biết backend nào thực sự xử lý yêu cầu.

```text
Forward proxy (đứng phía client)
  Máy nhân viên ──> Forward proxy ──> Internet ──> website.com
  website.com chỉ thấy IP của forward proxy

Reverse proxy (đứng phía server)
  Người dùng ──> Internet ──> Reverse proxy (shop.local) ──┬──> app-1 :9001
                                                           └──> app-2 :9002
  Người dùng chỉ thấy shop.local, không biết app nào phản hồi
```

Bạn cứ nhớ forward proxy che thông tin client, còn reverse proxy che thông tin server. Forward proxy thường do người dùng tự cài trên trình duyệt hoặc biến môi trường, trong khi reverse proxy do người vận hành website cấu hình sẵn và client cứ thế truy cập bình thường.

![Sơ đồ so sánh vị trí và vai trò của forward proxy, reverse proxy và API gateway trong hệ thống.](https://assets.200lab.io/sha256/a3/a3898c41b4368ec3d5e158d35111ab617a96da390efa8957cfef455bdccc2f56/generations/7d274132fa0d406548c47df45ea42d9c82f300bae7d1c7826b1590094f2ec510)

## Reverse proxy dùng để làm gì trong một website

Khi vận hành web, các công cụ như Nginx, HAProxy, Caddy hay Traefik thường được đặt làm reverse proxy để gánh các việc sau:

- **Nhận và giải mã HTTPS (TLS termination):** Reverse proxy xử lý chứng chỉ bảo mật ở vòng ngoài, giúp các backend trong mạng nội bộ chỉ cần giao tiếp bằng HTTP nhẹ nhàng.
- **Chia request cho nhiều backend:** Khi có nhiều server cùng chạy một app, proxy phân phối lượt gọi để tránh quá tải, tương tự cách làm trong bài [cân bằng tải với load balancer](https://200lab.io/blog/load-balancer-la-gi-chia-khoa-cho-hieu-suat-va-do-tin-cay).
- **Gom nhiều dịch vụ dưới một domain:** Bạn có thể chia đường dẫn để `/api/` đi vào backend xử lý logic, còn `/` trả thẳng file giao diện tĩnh.
- **Tối ưu tốc độ và bảo mật:** Hỗ trợ cache lại response, nén dữ liệu trước khi trả về và giấu cấu trúc server bên trong.

## Cấu hình reverse proxy bằng Nginx: file mẫu chạy được

Chưa quen Nginx thì bạn đọc trước bài tổng quan [Nginx là gì](https://200lab.io/blog/nginx-la-gi). Nginx làm reverse proxy bằng [directive proxy_pass](https://nginx.org/en/docs/http/ngx_http_proxy_module.html) đi kèm khối `upstream` khai báo nhóm backend, mặc định chia lượt theo kiểu round-robin (lần lượt từng server).

Mình đã chạy thử trên máy Linux với Nginx **1.31.8** cùng hai backend Python nhỏ. File `app.py` dùng `http.server`, nhận tên server và cổng từ dòng lệnh, trả về JSON chứa tên server, đường dẫn và các header nhận được.

```python
# app.py: backend nhỏ, in ra tên server và các header mà reverse proxy gửi kèm
import json, sys
from http.server import BaseHTTPRequestHandler, HTTPServer

NAME, PORT = sys.argv[1], int(sys.argv[2])

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        body = {
            "server": NAME,
            "path": self.path,
            "host": self.headers.get("Host"),
            "x_real_ip": self.headers.get("X-Real-IP"),
            "x_forwarded_for": self.headers.get("X-Forwarded-For"),
            "x_forwarded_proto": self.headers.get("X-Forwarded-Proto"),
        }
        data = json.dumps(body, indent=2).encode()
        self.send_response(200)
        self.send_header("Content-Type", "application/json")
        self.send_header("Content-Length", str(len(data)))
        self.end_headers()
        self.wfile.write(data)

    def log_message(self, *args):
        pass

HTTPServer(("127.0.0.1", PORT), Handler).serve_forever()
```

Trong file `/etc/nginx/conf.d/shop.conf`, khối `upstream shop_backend` gom hai cổng **9001** và **9002**. Ở khối `server` của domain `shop.local`, đường dẫn `/` trả file tĩnh tại `/var/www/shop`, còn `/api/` dùng `proxy_pass` đẩy vào `shop_backend` kèm bốn dòng `proxy_set_header` và timeout (**5s** kết nối, **30s** đọc dữ liệu).

```nginx
# /etc/nginx/conf.d/shop.conf
upstream shop_backend {
    server 127.0.0.1:9001;
    server 127.0.0.1:9002;
}

server {
    listen 80;
    server_name shop.local;

    location /api/ {
        proxy_pass http://shop_backend;

        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;

        proxy_connect_timeout 5s;
        proxy_read_timeout    30s;
    }

    location / {
        root  /var/www/shop;
        index index.html;
    }
}
```

Trên máy cá nhân, mình đổi `listen 80` thành **8080** để không cần quyền root, rồi bật hai backend Python ở cổng **9001** và **9002**. Lệnh `nginx -t` báo `syntax is ok` và `test is successful`, mình reload Nginx rồi dùng `curl` gọi thử.

```bash
# chạy 2 backend ở 2 cổng
python3 app.py app-1 9001 &
python3 app.py app-2 9002 &

# chép config vào Nginx, kiểm tra cú pháp rồi nạp lại
sudo cp shop.conf /etc/nginx/conf.d/shop.conf
sudo nginx -t
sudo nginx -s reload

# gọi 2 lần qua reverse proxy (bản chạy thử mình để listen 8080)
curl -H "Host: shop.local" http://127.0.0.1:8080/api/orders
curl -H "Host: shop.local" http://127.0.0.1:8080/api/orders
```

## Header Host, X-Real-IP và X-Forwarded-For khi đi qua reverse proxy

Khi gọi `/`, Nginx trả ngay `index.html` tĩnh mà không chạm tới Python. Gọi `/api/orders` hai lần liên tiếp, kết quả trả về lần lượt từ `app-1` rồi `app-2` đúng kiểu round-robin. Backend nhận đủ `host` là `"shop.local"`, `x_real_ip` và `x_forwarded_for` đều là `"127.0.0.1"`, còn `x_forwarded_proto` là `"http"`.

```json
{
  "server": "app-1",
  "path": "/api/orders",
  "host": "shop.local",
  "x_real_ip": "127.0.0.1",
  "x_forwarded_for": "127.0.0.1",
  "x_forwarded_proto": "http"
}
{
  "server": "app-2",
  "path": "/api/orders",
  "host": "shop.local",
  "x_real_ip": "127.0.0.1",
  "x_forwarded_for": "127.0.0.1",
  "x_forwarded_proto": "http"
}
```

Mặc định Nginx gửi header `Host` mang giá trị `$proxy_host`, tức tên trong `proxy_pass`. Mình thử xóa dòng `proxy_set_header Host $host` rồi reload, backend nhận ngay `host` là `"shop_backend"`. App nào dựa vào `Host` để sinh đường dẫn, chọn tenant hay kiểm tra domain hợp lệ sẽ chạy sai ngay.

```text
# bỏ dòng "proxy_set_header Host $host;" rồi reload, gọi lại:
"server": "app-1",
"host": "shop_backend",
```

Về IP, `X-Real-IP` lấy `$remote_addr` (IP kết nối trực tiếp tới Nginx), còn `X-Forwarded-For` dùng `$proxy_add_x_forwarded_for` nối IP client vào cuối header có sẵn. Mình thử dùng `curl` gắn header giả `X-Forwarded-For: 1.2.3.4`, backend nhận về `"1.2.3.4, 127.0.0.1"`. Phần đầu do client tự truyền nên dễ bị làm giả; ở backend bạn chỉ nên tin IP cuối do proxy của mình ghi vào hoặc `X-Real-IP`, và Nginx cũng có `ngx_http_realip_module` để lọc chuẩn phần này.

```bash
curl -H "Host: shop.local" -H "X-Forwarded-For: 1.2.3.4" http://127.0.0.1:8080/api/orders
```

```text
"x_real_ip": "127.0.0.1",
"x_forwarded_for": "1.2.3.4, 127.0.0.1",
"x_forwarded_proto": "http"
```

## Nginx xử lý thế nào khi một backend sau reverse proxy bị tắt

Khi chạy nhiều backend, chuyện một instance bị treo rất dễ xảy ra. Mình thử tắt `app-2` ở cổng **9002** rồi gọi lại API. Client vẫn nhận mã **200** từ `app-1`, vì khi thấy một server lỗi kết nối, Nginx tự chuyển request sang server còn sống trong nhóm upstream.

Cơ chế này dựa trên hai thông số [max_fails và fail_timeout](https://nginx.org/en/docs/http/ngx_http_upstream_module.html), mặc định `max_fails` bằng **1** và `fail_timeout` là **10 giây**. Khi mình tắt nốt `app-1` để cả hai backend cùng ngừng chạy, Nginx không còn nơi nào nhận request nên trả về lỗi **502 Bad Gateway**.

```bash
# tắt app-2, gọi lại
pkill -f "app.py app-2"
curl -s -o /dev/null -w "%{http_code}\n" -H "Host: shop.local" http://127.0.0.1:8080/api/orders
# 200 (Nginx chuyển sang app-1)

# tắt luôn app-1
pkill -f "app.py app-1"
curl -s -o /dev/null -w "%{http_code}\n" -H "Host: shop.local" http://127.0.0.1:8080/api/orders
# 502
```

## Reverse proxy và API gateway khác nhau thế nào

API gateway cũng đứng chắn trước backend giống reverse proxy, nhưng tập trung vào quản lý API: xác thực API key hoặc JWT, giới hạn lượt gọi (rate limiting), biến đổi hoặc gom request, quản lý version và thống kê theo từng khách hàng. Bạn có thể xem chi tiết trong bài [API gateway là gì](https://200lab.io/blog/api-gateway-la-gi).

Có thể coi API gateway là reverse proxy có thêm lớp xử lý logic cho API. Nhiều nơi kết hợp cả hai: đặt Nginx ở ngoài cùng giải mã TLS và trả file tĩnh, phía sau mới là API gateway lo xác thực và đếm rate limit.

| Tiêu chí | Forward proxy | Reverse proxy | API gateway |
| --- | --- | --- | --- |
| Đứng phía nào | Phía client (mạng nội bộ) | Phía server | Phía server (trước các API) |
| Ai cấu hình | Người dùng hoặc IT nội bộ | Người vận hành hệ thống | Người vận hành hệ thống |
| Che thông tin gì | Che IP và danh tính client | Che địa chỉ các backend | Che cấu trúc backend và microservices |
| Công việc chính | Kiểm soát truy cập ra Internet, cache | Giải mã HTTPS, chia tải, trả file tĩnh | Xác thực JWT/API key, rate limit, quản lý version |
| Ví dụ công cụ | Squid, cấu hình proxy trình duyệt | Nginx, HAProxy, Caddy, Traefik | Kong (trên nền Nginx/OpenResty), AWS API Gateway |

Nếu web chỉ có một backend, bạn dựng reverse proxy bằng Nginx là đủ có HTTPS và trả file tĩnh; khi tăng lên nhiều backend thì thêm khối `upstream`. Chỉ khi mở API cho đối tác, cần cấp key và giới hạn lượt gọi theo từng client mới cần tới API gateway; riêng forward proxy phục vụ phía client nên không thay thế được hai công cụ trên.

## Lỗi hay gặp khi cấu hình reverse proxy Nginx

Chỗ dễ nhầm nhất trên Nginx nằm ở dấu `/` cuối dòng `proxy_pass` vì cả hai cách viết đều hợp lệ cú pháp. Viết `proxy_pass http://shop_backend;` thì Nginx giữ nguyên `/api/orders` gửi vào backend; nhưng thêm dấu gạch chéo thành `proxy_pass http://shop_backend/;` thì phần `/api/` bị cắt bỏ, backend chỉ nhận được `/orders`.

```nginx
# Cách 1: không có dấu / cuối, backend nhận nguyên /api/orders
location /api/ {
    proxy_pass http://shop_backend;
}

# Cách 2: có dấu / cuối, /api/ được thay bằng /, backend nhận /orders
location /api/ {
    proxy_pass http://shop_backend/;
}
```

Một lỗi cũng rất hay gặp là **502 Bad Gateway**, do backend phía sau đã tắt hoặc khai báo lệch cổng trong file config. Một lỗi bảo mật khác là backend lấy thẳng giá trị đầu tiên trong `X-Forwarded-For` do client tự gửi lên để kiểm tra IP, dẫn tới bị qua mặt bộ đếm giới hạn lượt truy cập.

Theo mình, phần dễ quên nhất lúc viết config lại là mấy dòng `proxy_set_header`. Thiếu chúng thì lệnh kiểm tra Nginx vẫn báo đúng, gọi web vẫn trả về **200**, chỉ có điều backend âm thầm nhận sai `Host` và IP. Lỗi kiểu này chỉ lộ ra khi bạn soi log, thấy link sinh ra bị hỏng hoặc tính năng chặn IP hoạt động sai.

## Checklist cấu hình reverse proxy Nginx khi lên production

Chạy được trên máy local mới giải quyết phần nối cổng. Đưa reverse proxy ra hứng traffic thật, bạn cần rà lại vài điểm cốt lõi trong file config trước khi mở cổng cho user gọi vào:

- **Đóng cổng backend với bên ngoài:** Các cổng `9001`, `9002` chỉ mở cho mạng nội bộ hoặc localhost để ép mọi request đi qua Nginx ở cổng `80` và `443`.
- **Giữ đủ bốn dòng `proxy_set_header`:** Truyền đủ `Host`, `X-Real-IP`, `X-Forwarded-For` và `X-Forwarded-Proto` để code phía sau nhận đúng domain, IP và giao thức gốc.
- **Soi kỹ dấu `/` ở cuối `proxy_pass`:** Thừa hoặc thiếu một dấu gạch chéo là URI gửi vào backend bị cắt mất prefix ngay, dù cú pháp file config không báo lỗi gì.
- **Đặt timeout và chạy từ hai instance trở lên:** Khai báo rõ `proxy_connect_timeout`, `proxy_read_timeout` kèm nhiều server trong khối `upstream` để một con có treo thì Nginx còn chuyển request sang con khác, tránh trả thẳng `502 Bad Gateway` cho khách.
- **Chạy `nginx -t` trước mỗi lần reload:** Lệnh phải báo `syntax is ok` và `test is successful` rồi mới nạp lại cấu hình.

> ⚠️ **Chỗ dễ có vấn đề nhất:** backend lấy thẳng IP đầu tiên trong `X-Forwarded-For` để làm rate limit hoặc chặn truy cập, nhưng phần đầu chuỗi này client tự gắn được chỉ bằng một lệnh `curl`. Code backend chỉ được tin `X-Real-IP`, IP cuối do chính proxy của mình ghi vào, hoặc phải lọc qua `ngx_http_realip_module` trên Nginx.

> Đừng đợi tới lúc cần cân bằng tải mới dựng reverse proxy. Có một backend thôi thì cũng cứ cho Nginx đứng trước, sau này thêm HTTPS hay thêm server thì code backend không phải đụng vào.

Nếu bạn muốn đi sâu vào cách chọn giữa reverse proxy, load balancer, API gateway và ước lượng tải thì tham khảo khóa học bên dưới tại 200Lab nhé.

![Thiết kế hệ thống - ước lượng tải, load balancer, cache, queue, replication và partitioning](https://assets.200lab.io/sha256/0b/0b217d1bba119926a99dc02291da7d2364e83af165cddc8cc1e154a95f113634/generations/2b1ce89b70dc743a017321ed547b1217eabb7a6a0cb5f45746c5b4ebe8fb0357)

- [Thiết kế hệ thống - ước lượng tải, load balancer, cache, queue, replication và partitioning](https://200lab.io/courses/thiet-ke-he-thong-uoc-luong-tai-load-balancer-cache-queue-replication-va-partitioning.md)

Chúc anh em reload mượt mà và không gặp lỗi 502!

## Đọc thêm

- [Idempotency là gì? Idempotency key chống trừ tiền hai lần khi retry](https://200lab.io/blog/idempotency-key-la-gi.md): Idempotency key giúp POST retry an toàn khi timeout. Middleware Go chạy thật: giữ chỗ atomic, 409 khi trùng đồng thời, replay đúng response cũ.
- [Game System Design GonnaCrash được làm như thế nào](https://200lab.io/blog/game-system-design-gonnacrash-duoc-lam-nhu-the-nao.md): Kéo thả một hệ thống rồi nhìn nó trụ hay sập: cách mình dựng GonnaCrash, từ server làm trọng tài, con bot giải đề đến skin whiteboard.
- [Architecture Pattern - Phần 7: Hexagonal Architecture](https://200lab.io/blog/hexagonal-architecture-la-gi.md): Hexagonal Architecture là pattern kiến trúc mà mọi tương tác từ bên ngoài đều phải đi qua một Adapter để kết nối vào một Port của Application
- [File __init__.py trong Python: Hướng dẫn sử dụng toàn diện](https://200lab.io/blog/huong-dan-su-dung-toan-dien-file-init-trong-python.md): __init__.py không chỉ đơn thuần là một file đánh dấu package, nó quyết định cách package của bạn được cấu trúc, import và tối ưu ra sao
- [Thiết kế Leaderboard hiệu năng cao: Khi nào chọn SQL, Khi nào dùng Redis?](https://200lab.io/blog/thiet-ke-leaderboard-hieu-nang-cao-khi-nao-chon-sql-khi-nao-dung-redis.md): Khám phá cách thiết kế hệ thống Leaderboard tối ưu, linh hoạt, dễ mở rộng, kết hợp giữa tư duy thiết kế hệ thống hiện đại và kinh nghiệm thực tế
- [Mọi bài viết chủ đề Backend](https://200lab.io/blog/tags/backend.md)

Toàn bộ nội dung: https://200lab.io/llms.txt
