SSD Nodes Learn 🎉 VPS từ $5.50/tháng
Hướng dẫn Matt ConnorBởi Matt Connor

Cấu hình Gluetun port forwarding cho torrent

Download vẫn chạy nhưng không có kết nối vào? Cấu hình port forwarding trong Gluetun, cập nhật port mới cho torrent client sau mỗi lần reconnect và kiểm tra kết quả.

Vì sao không có kết nối đến nếu không forward port

Port forwarding của Gluetun yêu cầu VPN provider ánh xạ một port public trên địa chỉ exit của provider về container của bạn. Đây là cách duy nhất để peer khác có thể bắt đầu kết nối đến torrent client. Nếu không có ánh xạ này, tunnel vẫn hoạt động, việc download vẫn chạy, nhưng không có kết nối nào tự đến. Mọi kết nối đang hoạt động đều do client của bạn chủ động mở trước.

Cơ chế này là NAT (network address translation). Container của bạn dùng chung địa chỉ exit của provider với nhiều khách hàng khác. Khi client mở kết nối ra ngoài, provider ghi nhận flow đó rồi gửi các gói phản hồi trở lại qua tunnel. Kết nối từ một peer lạ đi vào không khớp với flow nào đã được ghi nhận, nên packet đến địa chỉ exit rồi bị drop tại đó. Client của bạn vẫn kết nối được đến mọi peer vốn đã có thể nhận kết nối, vì vậy download vẫn hoàn tất và vấn đề không dễ nhận ra. Vấn đề thể hiện rõ khi seeding, vì seeder là máy mà người khác phải kết nối đến.

Một port inbound đang mở sẽ thay đổi 2 điều. Bạn tham gia swarm nhanh hơn, vì những peer không tự nhận được kết nối giờ có thể kết nối đến bạn. Đồng thời, bạn có thể upload cho các peer đó.

Vì sao hầu hết nhà cung cấp VPN không cung cấp port forwarding

Port được forward là tài nguyên khan hiếm trên một địa chỉ dùng chung. Nhà cung cấp dành riêng một số port trên một exit IP cho một khách hàng, rồi chịu trách nhiệm xử lý mọi hoạt động của khách hàng đó qua port này. Một số nhà cung cấp lớn đã bỏ tính năng này và nêu lý do là phải xử lý abuse. Hãy xem port forwarding là một câu hỏi về phạm vi hỗ trợ, không phải một checkbox: hỏi nhà cung cấp hiện có cung cấp port forwarding không, gói của bạn có được hỗ trợ không, và tính năng này có dùng được trên các server mà bạn thực sự chọn được không.

Khi có hỗ trợ forwarding, port thường là port động. Port này thuộc về VPN session chứ không thuộc về account của bạn, nên có thể đổi sau mỗi lần reconnect. Private Internet Access cấp một port có chữ ký để gluetun refresh; tài liệu upstream cho biết bạn giữ được cùng port trong 60 ngày nếu bind mount thư mục /gluetun để state được giữ lại sau khi restart. ProtonVPN cấp một port ngẫu nhiên qua NAT-PMP (NAT port mapping protocol) với lease ngắn và phải liên tục gia hạn. Vì vậy, đặt port một lần trong client sẽ không duy trì được kết nối.

Những nhà cung cấp nào mà gluetun có thể yêu cầu cấp port

Tính đến gluetun v3.41.3, được phát hành ngày 30 July 2026, integration native xác thực 4 tên nhà cung cấp: Private Internet Access, ProtonVPN, Perfect Privacy và PrivateVPN. Bật integration này bằng VPN_PORT_FORWARDING=on; mặc định giá trị là off. Các hướng dẫn cũ dùng PORT_FORWARDING hoặc PRIVATE_INTERNET_ACCESS_VPN_PORT_FORWARDING. Cả hai vẫn hoạt động trong phiên bản này dưới dạng tên tương thích ngược, nhưng đều sắp bị loại bỏ.

Hai chi tiết của nhà cung cấp quyết định request có thể thành công hay không. ProtonVPN yêu cầu gói trả phí và phải bật NAT-PMP: bật NAT-PMP (Port Forwarding) trong phần tùy chọn VPN khi tạo cấu hình WireGuard, hoặc thêm +pmp vào username khi dùng OpenVPN. Với Private Internet Access trên OpenVPN, PORT_FORWARD_ONLY giới hạn việc chọn server vào các server hỗ trợ port forwarding, để bạn không kết nối vào server vốn không hỗ trợ tính năng này. WireGuard và OpenVPN khác nhau về cách yêu cầu cấp port, vì vậy hãy đọc trang hướng dẫn của nhà cung cấp trước khi chọn.

Khi gluetun chạy cấu hình tùy chỉnh thay vì provider tích hợp sẵn, VPN_PORT_FORWARDING_PROVIDER chỉ định API mà gluetun cần gọi. Trang Private Internet Access upstream ghép biến này với VPN_PORT_FORWARDING_USERNAMEVPN_PORT_FORWARDING_PASSWORD; các biến này chứa thông tin xác thực tài khoản cần cho request cấp port.

Bật chuyển tiếp cổng của gluetun trong docker compose

Phần này giả định tunnel đã hoạt động. Nếu chưa, trước tiên hãy làm theo định tuyến lưu lượng của container Docker qua gluetun, rồi quay lại khi các lượt tải xuống đã chạy.

services:
  gluetun:
    image: qmcgaw/gluetun:v3.41.3
    container_name: gluetun
    cap_add:
      - NET_ADMIN
    devices:
      - /dev/net/tun:/dev/net/tun
    ports:
      - 8080:8080/tcp
      - 8000:8000/tcp
    volumes:
      - ./gluetun:/gluetun
    environment:
      - VPN_SERVICE_PROVIDER=protonvpn
      - VPN_TYPE=wireguard
      - WIREGUARD_PRIVATE_KEY=${WIREGUARD_PRIVATE_KEY}
      - VPN_PORT_FORWARDING=on
      - TZ=Etc/UTC
    restart: unless-stopped

  qbittorrent:
    image: lscr.io/linuxserver/qbittorrent:5.2.3
    container_name: qbittorrent
    network_mode: "service:gluetun"
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Etc/UTC
      - WEBUI_PORT=8080
    volumes:
      - ./qbittorrent:/config
      - ./downloads:/downloads
    depends_on:
      - gluetun
    restart: unless-stopped

Ghim tag. qmcgaw/gluetun:latest theo dõi nhánh master, nơi phần xử lý chuyển tiếp cổng đang được thay đổi cho v4, vì vậy image không được ghim có thể thay đổi hành vi ở lần docker compose pull tiếp theo. Giữ private key ngoài file compose bằng cách dùng file env cho secret của compose.

Gluetun ghi cổng được forward ở đâu

Gluetun cung cấp cổng ở ba nơi và cả ba nơi đều chứa cùng một giá trị.

Gluetun ghi log cổng một lần cho mỗi lần lấy cổng. Dòng log có dạng port forwarded is 45678. Nếu request không trả về giá trị nào, dòng log có dạng no port forwarded.

docker logs gluetun 2>&1 | grep -i "port forwarded"

Gluetun ghi số cổng vào file được chỉ định bởi VPN_PORT_FORWARDING_STATUS_FILE. Mặc định, đây là /tmp/gluetun/forwarded_port. File chứa một cổng trên mỗi dòng, được ghi với mode 0644 và được chown cho PUIDPGID của container. Khi forwarding dừng, gluetun xóa nội dung file thay vì xóa file. Nhờ đó, consumer có thể đọc một file rỗng thay vì gặp lỗi file không tồn tại.

docker exec gluetun cat /tmp/gluetun/forwarded_port

Gluetun cung cấp giá trị này trên control server. Control server mặc định lắng nghe tại :8000 và được cấu hình bằng HTTP_CONTROL_SERVER_ADDRESS.

curl -s http://127.0.0.1:8000/v1/portforward
{"port":45678,"ports":[45678]}

Gluetun cũng mở cổng đó trong firewall của chính nó trên VPN interface. Vì vậy, không cần FIREWALL_VPN_INPUT_PORTS khi native integration đang xử lý việc này. Biến đó dùng cho trường hợp còn lại: provider mà gluetun không thể query, trong khi bạn được cấp một cổng tĩnh qua một kênh khác và phải tự allow cổng đó.

Trong ba cách này, một cách giữ được dữ liệu lâu dài và hai cách còn lại thì không. Tài liệu upstream đánh dấu status file là deprecated từ v4.0.0. Ngoài ra, GET /v1/openvpn/portforwarded đã trả về 301 Moved Permanently và trỏ đến /v1/portforward. Code mới nên đọc từ control server.

Vì sao phải thông báo port cho client sau mỗi lần reconnect

Torrent client lưu port listening trong cấu hình riêng và giữ nguyên số port đó qua các lần restart. Port được forward là thuộc tính của VPN session. Sau khi reconnect, hai số port không còn khớp nhau. Provider sẽ map một port không có tiến trình nào listening, còn client lại listening trên một port không được map. Reconnect không phải trường hợp hiếm: container restart, server thay đổi, tunnel bị ngắt khiến health check của gluetun restart tunnel, hoặc lease không thể renew. Kết quả là setup hôm qua vẫn truy cập được nhưng hôm nay âm thầm không còn truy cập được, trong khi cả hai log đều không báo lỗi.

Vì vậy, phải apply port ngay khi gluetun acquire port đó. Có hai cách để kết nối cơ chế này, và điểm khác nhau là process nào thực hiện công việc.

Tùy chọn 1: gluetun đẩy port bằng lệnh up

VPN_PORT_FORWARDING_UP_COMMAND chạy khi port forwarding được thiết lập, còn VPN_PORT_FORWARDING_DOWN_COMMAND chạy khi port forwarding bị hủy. Gluetun thay thế {{PORT}} (port đầu tiên), {{PORTS}} (tất cả port, phân tách bằng dấu phẩy) và {{VPN_INTERFACE}} (tên tunnel interface, mặc định là tun0) trước khi chạy lệnh. Cú pháp shell cần một wrapper /bin/sh -c rõ ràng. Đây là ví dụ qBittorrent từ upstream, được viết thành hai mục môi trường trong compose:

      - VPN_PORT_FORWARDING_UP_COMMAND=/bin/sh -c 'wget -O- -nv --retry-connrefused --post-data "json={\"listen_port\":{{PORT}},\"current_network_interface\":\"{{VPN_INTERFACE}}\",\"random_port\":false,\"upnp\":false}" http://127.0.0.1:8080/api/v2/app/setPreferences'
      - VPN_PORT_FORWARDING_DOWN_COMMAND=/bin/sh -c 'wget -O- -nv --retry-connrefused --post-data "json={\"listen_port\":0,\"current_network_interface\":\"lo\"}" http://127.0.0.1:8080/api/v2/app/setPreferences'

Mỗi trường trong lệnh gọi này có một chức năng. listen_port là port mới. current_network_interface bind qBittorrent vào tunnel. Đặt random_port thành false để qBittorrent không tự chọn port khác ở lần khởi động tiếp theo. Đặt upnp thành false để qBittorrent không cố map port qua một router không tồn tại.

Cách này có hai yêu cầu. Web UI của qBittorrent phải phản hồi trên 127.0.0.1:8080 từ bên trong container gluetun. Điều này tự động đúng khi client dùng chung network namespace của gluetun. Ngoài ra, Bypass authentication for clients on localhost (bypass_local_auth) phải được bật vì lệnh không gửi credentials. Lệnh down cần thiết vì qBittorrent không phải lúc nào cũng thiết lập lại port sau khi bị ngắt kết nối.

Lệnh chạy bên trong container gluetun. Image này được xây dựng trên Alpine và có sẵn wget. Image đó không có curl. Lệnh gọi một binary không tồn tại trong image sẽ luôn fail mỗi khi port forwarding được thiết lập.

Tùy chọn 2: một process bên ngoài gluetun đọc port

Mô hình còn lại chạy một process nhỏ bên cạnh gluetun. Process này lấy port rồi truyền port vào client thông qua API riêng của client. Đọc port từ control server:

port=$(curl -s http://127.0.0.1:8000/v1/portforward | jq -r .port)

Hoặc đọc file nếu process có thể truy cập file đó. /tmp/gluetun/forwarded_port nằm bên trong container gluetun, nên sidecar cần một shared volume được mount tại /tmp/gluetun trong cả hai container. Bạn cũng có thể trỏ VPN_PORT_FORWARDING_STATUS_FILE đến một đường dẫn bên dưới volume đã được mount.

Authentication rất quan trọng ở đây. Trong v3.41.3, route GET /v1/portforward thuộc về role mặc định có tên public với auth = "none", nên route này trả lời mà không cần credentials. gluetun cũng ghi một cảnh báo bắt đầu bằng route GET /v1/portforward is unprotected by default, please set up authentication. Upstream sẽ đóng lối này trong một release sau. Hãy định nghĩa role ngay bây giờ trong file được bind mount tại /gluetun/auth/config.toml:

roles = [
  { name = "qbittorrent", routes = ["GET /v1/portforward"], auth = "apikey", apikey = "myapikey" }
]

Tạo key bằng docker run --rm qmcgaw/gluetun:v3.41.3 genkey rồi gửi key trong header X-API-Key. HTTP_CONTROL_SERVER_AUTH_DEFAULT_ROLE thực hiện cùng chức năng như một biến môi trường được mã hóa bằng JSON nếu bạn không muốn mount file. Publish port 8000 mà không có role sẽ cho bất kỳ ai truy cập được port này quyền điều khiển trạng thái VPN. Vì vậy, hãy chủ động quyết định port này được truy cập từ đâu khi xác định cách truy cập gluetun từ host và các container khác.

Chọn lệnh up khi client cung cấp API mà một lần gọi wget có thể xử lý. Lệnh này chỉ chạy đúng một lần cho mỗi event và không cần process tiếp tục chạy. Chọn process bên ngoài khi client cần login flow, ghi lại file cấu hình hoặc restart. Trong một arr stack phía sau một container gluetun, cách triển khai thường là một poller nhỏ, vì chỉ torrent client cần port này.

Bẫy thường gặp: dùng chung network namespace không tự đặt listening port

Lỗi này làm mất nhiều thời gian nhất. network_mode: "service:gluetun" đưa client vào network namespace của gluetun, nên client có địa chỉ VPN, các route của tunnel và các firewall rule của gluetun. Không điều nào trong số đó đặt listening port cho client. Gluetun mở forwarded port trên interface VPN, các packet gửi đến cổng này đi vào namespace, nhưng nếu client listen trên một port khác thì kernel không có đích để chuyển chúng đến. Kết nối bị từ chối hoặc timeout trong khi mọi kiểm tra outbound đều cho kết quả bình thường. Forwarded port và listening port của client là hai số riêng biệt. Nhiệm vụ duy nhất là giữ cho hai số này giống nhau.

Hãy so sánh hai port thay vì đoán. Cả hai lệnh đều chạy trên cùng một namespace:

docker exec gluetun cat /tmp/gluetun/forwarded_port
docker exec gluetun wget -qO- http://127.0.0.1:8080/api/v2/app/preferences | grep -o '"listen_port":[0-9]*'

Có thêm một setting khiến nhiều người đi sai hướng. VPN_PORT_FORWARDING_LISTENING_PORT dùng iptables để chuyển traffic inbound từ forwarded port đến một local port cố định. Tài liệu upstream khuyên không dùng setting này với torrent client, vì client tự announce listening port của nó cho tracker và peer. Khi đó swarm sẽ biết sai port.

Cách xác nhận cổng được chuyển tiếp có thể truy cập

Chỉ báo kết nối của chính client phản ánh các kết nối outbound đến tracker, nên nó có thể hiển thị màu xanh dù không có kết nối inbound nào đến được máy bạn. Hãy kiểm tra bằng một listener do bạn kiểm soát, từ một network nằm ngoài tunnel. Upstream có phát hành một công cụ nhỏ cho việc này. Trước tiên hãy dừng torrent client, vì hai process không thể bind cùng một port.

docker stop qbittorrent
docker exec -it gluetun /bin/sh

Bên trong container, thay amd64 bằng architecture của CPU và 4567 bằng port được chuyển tiếp của bạn:

wget -qO port-checker https://github.com/qdm12/port-checker/releases/download/v0.4.0/port-checker_0.4.0_linux_amd64
chmod +x port-checker
./port-checker --listening-address=":4567"

Bây giờ hãy tìm địa chỉ exit mà gluetun đang sử dụng. Response có định dạng JSON và địa chỉ nằm trong trường public_ip.

curl -s http://127.0.0.1:8000/v1/publicip/ip

Mở http://<that address>:4567 từ một thiết bị không nằm trong cùng VPN. Điện thoại dùng mobile data là đủ. Một trang hiển thị IP address và user agent của browser, cùng với request tương ứng được ghi trong log của port-checker, cho biết inbound TCP đã đến được namespace. Timeout cho biết kết nối không đến được, và nguyên nhân nằm ở lớp phía trên client. Dừng công cụ bằng CTRL+C, thoát shell bằng exit, rồi khởi động lại client. Kiểm tra này chỉ kiểm tra TCP. Lưu lượng DHT (distributed hash table) và uTP sử dụng UDP trên cùng số port, nhưng test này không kiểm tra phần đó.

Các dạng lỗi và chuỗi bạn sẽ thấy

Log hoàn toàn không có dòng port. Không có thành phần nào yêu cầu port. Dùng docker exec gluetun printenv | grep PORT_FORWARDING để xác nhận biến đã thực sự được truyền vào container chưa, vì đặt biến trong nhầm compose service là nguyên nhân thường gặp.

Gluetun từ chối khởi động và báo lỗi provider. VPN_PORT_FORWARDING_PROVIDER được kiểm tra với 4 tên được hỗ trợ, nên lỗi gõ sai sẽ làm container dừng thay vì âm thầm chạy mà không forward port.

Log hiển thị no port forwarded. Gluetun đã gửi yêu cầu nhưng provider không trả về port. Với ProtonVPN, nguyên nhân thường là NAT-PMP chưa được bật trong cấu hình bạn đã tạo, hoặc gói dịch vụ không hỗ trợ port forwarding. Với Private Internet Access, nguyên nhân thường là server đã chọn không cung cấp tính năng này.

Đã nhận được port nhưng không có kết nối vào. Dùng 2 lệnh ở trên để so sánh forwarded port với listening port của client. Nếu 2 port khớp nhau, kiểm tra xem client đã bind vào tunnel interface chưa và tùy chọn random-port đã tắt chưa, vì tùy chọn này sẽ thay đổi listening port mỗi lần khởi động.

Lệnh up có vẻ không làm gì. Chạy đúng lệnh bên trong container để xem lỗi: docker exec gluetun /bin/sh -c '<your command>'. curl: not found thường là kết quả vì image chỉ cung cấp wget.

401 Unauthorized từ control server. Bạn đã định nghĩa auth config nhưng role không liệt kê route mà bạn đang gọi. Route được đối chiếu theo method cộng với path, nên role chỉ liệt kê /v1/portforward sẽ không bao phủ GET /v1/portforward.

Private Internet Access cấp port khác sau mỗi lần restart. Bind mount /gluetun để trạng thái port đã lưu được giữ lại sau restart. Nếu không có volume này, gluetun sẽ yêu cầu port mới mỗi lần.

FAQ

Vì sao torrent tải được nhưng không bao giờ nhận kết nối đến?

Nếu không forward port, nhà cung cấp VPN không có NAT rule để chuyển các packet đến trên bất kỳ port nào vào tunnel của bạn. Vì vậy, các kết nối mà bạn không chủ động mở sẽ bị drop tại địa chỉ exit. Việc tải xuống vẫn hoạt động vì client tự mở các kết nối đó và có thể kết nối đến mọi peer có thể nhận kết nối. Seeding và tham gia swarm bị ảnh hưởng vì cả hai đều phụ thuộc vào việc các máy khác kết nối đến bạn. Cách khắc phục là dùng nhà cung cấp có hỗ trợ port forwarding, VPN_PORT_FORWARDING=on trong gluetun, rồi áp dụng port nhận được vào listening port của client.

gluetun có hoạt động với port forwarding của mọi nhà cung cấp VPN không?

Không. gluetun v3.41.3 tích hợp native với 4 nhà cung cấp: Private Internet Access, ProtonVPN, Perfect Privacy và PrivateVPN. Mọi nhà cung cấp ngoài danh sách đó đều không vượt qua bước validation cho VPN_PORT_FORWARDING_PROVIDER, và container sẽ dừng khi khởi động. Nếu nhà cung cấp cấp một port tĩnh qua control panel riêng, gluetun không thể yêu cầu port đó thay bạn. Tuy nhiên, FIREWALL_VPN_INPUT_PORTS sẽ cho phép port cố định đó đi qua firewall của gluetun. Chính sách của nhà cung cấp có thể thay đổi, vì vậy hãy kiểm tra trang hiện tại của nhà cung cấp trước khi mua plan cho mục đích này.

Tôi có phải cập nhật port sau mỗi lần reconnect không?

Có, và việc cập nhật này nên được tự động hóa. Port được forward thuộc về VPN session. Vì vậy, việc restart container, thay đổi server hoặc gia hạn lease thất bại có thể tạo ra một số port mới, trong khi client vẫn giữ port cũ trong cấu hình riêng. Bạn có thể để gluetun đẩy port bằng VPN_PORT_FORWARDING_UP_COMMAND. Lệnh này chạy ngay khi port forwarding hoạt động. Hoặc chạy một process nhỏ để đọc GET /v1/portforward từ control server rồi ghi giá trị đó vào client thông qua API của client.

Làm thế nào để kiểm tra port được forward thực sự mở?

Chạy một listener trên đúng port đó bên trong network namespace của gluetun, rồi kết nối đến listener từ bên ngoài VPN. Trước tiên, hãy dừng torrent client để giải phóng port. Sau đó, chạy binary port-checker upstream bên trong container gluetun bằng --listening-address=":<port>". Lấy exit address bằng curl -s http://127.0.0.1:8000/v1/publicip/ip rồi mở http://<address>:<port> trên điện thoại đang dùng mobile data. Nếu log của port-checker xuất hiện request, điều đó chứng minh inbound TCP đã đến nơi. Nếu bị timeout thì port không nhận được kết nối, bất kể status icon của client hiển thị gì.

#gluetun#vpn#port-forwarding#Docker#torrenting