SSD Nodes Learn Hosting plans →
Hướng dẫn Matt ConnorBởi Matt Connor · Cập nhật ngày 2026-08-27

Cài onion site trên VPS với Tor và nginx

Chạy onion service v3 trên Ubuntu bằng Tor và nginx bind vào loopback, sau đó kiểm tra các lỗi cấu hình có thể làm lộ địa chỉ public IP của VPS.

Bạn đang xây dựng gì

Một onion site là một web server bình thường chỉ phản hồi qua mạng Tor. Cài tor, thêm 2 dòng vào /etc/tor/torrc, đọc địa chỉ mà tor ghi ra, rồi bind nginx vào 127.0.0.1 để không có dịch vụ nào phản hồi trên public IP. Phần cài đặt mất 10 phút. Phần còn lại của hướng dẫn này là danh sách các điểm có thể làm lộ thông tin, vì onion site thường bị lỗi khi chính cấu hình của nó trỏ thẳng về operator.

Tor ban đầu có tên là "the onion router", còn onion service là dịch vụ chỉ có thể truy cập thông qua Tor. Địa chỉ version 3 có 56 ký tự theo sau là .onion. Các ký tự đó gồm public key ed25519 của service, checksum và một byte version, được mã hóa bằng base32. Địa chỉ version 2 (16 ký tự) đã bị loại khỏi network vào năm 2021, nên mọi địa chỉ bạn tạo hôm nay đều là v3. Địa chỉ chính là key, dẫn đến 2 hệ quả. Kết nối được mã hóa và xác thực end-to-end mà không cần certificate authority, còn mất file key đồng nghĩa với mất vĩnh viễn địa chỉ đó.

Server của bạn không bao giờ nhận kết nối inbound. Tor chọn một số relay làm introduction point, upload descriptor đã ký lên directory server, rồi gặp từng visitor tại một rendezvous relay do visitor chọn. Tất cả các kết nối này đều là outbound từ máy chủ của bạn. Bạn không cần mở port và cũng không cần publish DNS record. Nếu muốn cùng VPS đó cung cấp tài nguyên cho network, đó là một công việc khác với mô hình ngược lại, vì obfs4 bridge nhận kết nối inbound từ các client bị kiểm duyệt và cần một port có thể truy cập được.

Cài đặt tor từ repository của Tor Project

Ubuntu có package tor trong repository universe, nhưng package này thường giữ gần phiên bản tại thời điểm bản release bị đóng băng. Repository chính thức của Tor Project theo dõi bản stable hiện tại. Đây là phiên bản phù hợp cho phần mềm quyết định địa chỉ của bạn có tiếp tục thuộc về bạn hay không. Việc một package có trong một distribution không cho biết còn ai duy trì nó hay không. Bạn nên kiểm tra điều này với mọi phần mềm tự host, vì Searx không có commit code nào từ năm 2023, trong khi SearXNG tiếp tục phát triển dự án đó.

sudo apt update
sudo apt install -y apt-transport-https gnupg wget
KEYURL=https://deb.torproject.org/torproject.org/A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89.asc
wget -qO- "$KEYURL" | gpg --dearmor | sudo tee /usr/share/keyrings/deb.torproject.org-keyring.gpg >/dev/null

Mục repository dùng định dạng deb822 và Suites phải là codename của Ubuntu. Hãy đọc giá trị này từ /etc/os-release thay vì tự nhập, vì codename sai sẽ tạo ra một repository vẫn resolve bình thường nhưng không có package cho bản release của bạn.

. /etc/os-release
sudo tee /etc/apt/sources.list.d/tor.sources >/dev/null <<EOF
Types: deb deb-src
URIs: https://deb.torproject.org/torproject.org/
Suites: $VERSION_CODENAME
Components: main
Signed-By: /usr/share/keyrings/deb.torproject.org-keyring.gpg
EOF
sudo apt update
sudo apt install -y tor deb.torproject.org-keyring

Package deb.torproject.org-keyring giữ signing key luôn được cập nhật, nên việc xoay vòng key sẽ không làm apt update hỏng sau một năm. Kiểm tra tor đã khởi động và kết nối được đến network:

tor --version
sudo journalctl -u tor@default -n 20

Journal phải kết thúc bằng Bootstrapped 100% (done): Done. Nếu tor bị kẹt ở Bootstrapped 10% thì nó không có đường outbound. Hãy kiểm tra network firewall của nhà cung cấp và các egress rule của bạn: sudo ufw status verbose phải hiển thị allow (outgoing) là mặc định.

Từ đây cần nhớ hai tên. Package chạy tor bằng user debian-tor, còn unit đang chạy là tor@default.service, vì tor.service trên Debian và Ubuntu chỉ là wrapper cho instance. Hãy yêu cầu status và log theo tên instance để luôn nhận được process thực tế.

Cấu hình onion service trong torrc

Thêm 2 dòng vào /etc/tor/torrc:

HiddenServiceDir /var/lib/tor/onion_site/
HiddenServicePort 80 127.0.0.1:8080

HiddenServiceDir là nơi tor lưu các key và địa chỉ của service này. Không tự tạo thư mục này. Khi khởi động, Tor sẽ tự tạo thư mục với owner và mode cần thiết. Nếu bạn tạo thư mục bằng root, lỗi đầu tiên trong danh sách lỗi bên dưới sẽ xảy ra.

HiddenServicePort có 2 phần. Nhầm 2 phần này là lỗi đầu tiên thường gặp. Số thứ nhất là cổng mà client kết nối đến bên trong tunnel. Vì vậy, 80 là giá trị thường dùng và không có lý do gì để thay đổi. Phần thứ hai là địa chỉ local mà tor chuyển tiếp traffic đến. Nếu chỉ ghi HiddenServicePort 80, tor sẽ chuyển tiếp đến 127.0.0.1:80. Vì vậy, ghi rõ địa chỉ và dùng một cổng cao sẽ giúp onion vhost không đụng với dịch vụ đang listen trên cổng 80.

sudo systemctl restart tor@default
sudo ls -l /var/lib/tor/onion_site/

Kết quả liệt kê phải có hostname, hs_ed25519_public_key, hs_ed25519_secret_key và một thư mục authorized_clients rỗng.

Đọc địa chỉ .onion của bạn

sudo cat /var/lib/tor/onion_site/hostname

Một dòng được trả về: 56 ký tự base32 và .onion. Chuỗi đó là toàn bộ danh tính của site. Không ai cấp, chuyển nhượng hoặc lấy nó khỏi bạn khi bạn còn giữ file key. Hãy sao chép ngay, vì mọi cấu hình bên dưới đều cần chuỗi này. Phần còn lại của hướng dẫn này viết chuỗi đó là <your-address>.onion.

Phục vụ site bằng nginx bind vào 127.0.0.1

sudo apt install -y nginx
sudo install -d -m 755 /srv/onion

Ghi /etc/nginx/sites-available/onion:

server {
    listen 127.0.0.1:8080;
    server_name <your-address>.onion;

    root /srv/onion;
    index index.html;

    server_tokens off;
    etag off;
    access_log off;
    error_log /var/log/nginx/onion.error.log error;
}
echo '<h1>hello from the onion</h1>' | sudo tee /srv/onion/index.html
sudo ln -s /etc/nginx/sites-available/onion /etc/nginx/sites-enabled/onion
sudo nginx -t
sudo systemctl reload nginx

Bây giờ xác minh 2 điều từ server. Thứ nhất, nginx trả lời cho onion name. Đây là header Host chính xác mà tor sẽ gửi:

curl -s -H 'Host: <your-address>.onion' http://127.0.0.1:8080/

Thứ hai, nginx chỉ trả lời tại đó và không trả lời ở nơi nào khác:

sudo ss -tlnp | grep 8080

Cột address phải là 127.0.0.1:8080. Nếu là 0.0.0.0:8080 hoặc *:8080, onion site của bạn cũng đang được public trên Internet. Đây là mục đầu tiên trong danh sách rò rỉ. Một dòng listen 8080; không có address sẽ bind vào mọi interface. Đây là giá trị mặc định.

Mở address đó bằng Tor Browser. Lần tải đầu tiên mất vài giây vì client phải fetch descriptor của bạn và xây dựng rendezvous circuit.

Tài liệu chính thức của Tor Project ưu tiên unix socket thay vì loopback port: HiddenServicePort 80 unix:/var/run/tor/onion_site.sock, trong đó nginx listen trên path đó. Host khác hoàn toàn không thể truy cập socket, kể cả khi sau này máy có thêm interface thứ hai. Đổi lại, bạn phải xử lý file permission, vì nginx tạo socket còn tor kết nối tới socket đó bằng debian-tor. Do đó, 2 user phải có quyền phù hợp trên directory. Dùng loopback kèm output ss đã xác minh sẽ dễ cấu hình đúng hơn. Phần còn lại của guide này giả định dùng cách đó.

Khi site chỉ chạy trên loopback, box hoàn toàn không cần inbound rule cho site này. Giữ port 22 mở để bạn truy cập và deny các port còn lại (các ufw default nên đặt trên VPS). Lưu ý, firewall không thể vô hiệu hóa một service bind vào 0.0.0.0. Firewall chỉ lọc các packet đi tới firewall. Container làm vấn đề này nghiêm trọng hơn, vì publish Docker port sẽ ghi rule iptables trước ufw. Vì vậy, -p 8080:80 sẽ đưa onion backend lên public IP, trong khi ufw vẫn báo port đó bị deny. Hãy publish container port bằng -p 127.0.0.1:8080:80.

Các điểm rò rỉ làm mất tính ẩn danh của onion site

Tor che giấu vị trí của server. Tor không che giấu nội dung server gửi ra. Mỗi mục dưới đây là thông tin do chính stack của bạn công khai. Đọc tài liệu của từng công cụ privacy để biết chính xác phạm vi bảo vệ, thay vì dựa vào danh tiếng của nó, cũng là thói quen giúp bạn hiểu SearXNG tự host che giấu điều gì, tức là chỉ che giấu IP của bạn với các search engine, không hơn.

Chính site đó trả lời trên public IP của bạn

Đây là lỗi khiến nhiều người bất ngờ. Các scanner liên tục lập chỉ mục HTTP response của mọi địa chỉ có thể route được, và các kết quả đó đều public, có thể tìm kiếm. Nếu bạn phục vụ cùng một trang trên public IP và onion address, việc liên kết chúng chỉ cần một truy vấn: cùng title, cùng favicon hash, cùng ETag, cùng thứ tự header. Dòng listen 127.0.0.1:8080; ở trên là cách xử lý. Hãy kiểm tra từ một máy khác, không kiểm tra từ chính server:

curl -sv --max-time 5 http://<your-public-ip>:8080/

Connection refused hoặc timeout mới là kết quả đúng. Bất kỳ HTML nào cũng có nghĩa là site đang public. Nếu máy này đồng thời chạy một clearnet site, hãy đặt root riêng cho vhost đó và giữ một block default_server rõ ràng trên public listener, để header Host không khớp không thể rơi xuống onion vhost.

curl -sI http://127.0.0.1:8080/ | grep -i '^server'

nginx mặc định trả về Server: nginx/1.24.0. Chuỗi phiên bản đó, cùng với thứ tự chính xác của các header khác, tạo thành fingerprint có thể dùng để đối chiếu onion site với clearnet host. server_tokens off; rút gọn nó thành Server: nginx. Cấu hình này không xóa header, và nginx không có directive tích hợp để làm việc đó, nên module headers-more thường là lựa chọn phù hợp nếu bạn muốn xóa hẳn. PHP thêm X-Powered-By cho đến khi bạn đặt expose_php = Off. etag off; cũng cần nằm trong cùng danh sách, vì nginx tạo ETag từ thời gian sửa đổi và kích thước file; do đó, cùng một file được copy lên 2 server sẽ tạo cùng ETag trên cả 2 server.

URL tuyệt đối trỏ đến clearnet domain của bạn

Một thẻ rel="canonical", một og:url của Open Graph, RSS feed, sitemap, email đặt lại password hoặc URL logo hardcoded. Chỉ một mục trong số đó cũng đủ ghi tên clearnet site vào trang được phục vụ qua onion. Dùng các path tương đối với root như /static/logo.svg, và để application đọc base URL từ request host thay vì dùng một giá trị cố định. Redirect cũng là cùng một lỗi nhưng ở vị trí khác: return 301 https://example.com$request_uri; trong catch-all block sẽ chuyển onion visitor đến domain thật của bạn, còn header Location gửi trực tiếp thông tin đó cho họ.

Dùng chung TLS certificate với clearnet site

Onion address tự xác thực vì address đó chính là public key, nên http:// qua kết nối onion vốn đã được mã hóa end-to-end và Tor Browser xem đây là secure context. Cài certificate hiện có lên onion vhost sẽ công khai mối liên hệ giữa 2 site, vì mọi certificate được public trust đều được ghi vào Certificate Transparency logs, và các log này là public, tồn tại lâu dài, đồng thời có thể tìm kiếm theo tên. Hãy giữ Let's Encrypt certificate trên clearnet vhost và để onion vhost chạy plain HTTP.

Font và analytics của bên thứ ba

Một font từ CDN (content delivery network) hoặc một analytics script. Browser của visitor sẽ fetch từng tài nguyên trực tiếp, nên bên thứ ba biết có người đã load trang của bạn và thường biết cả trang nào, trong khi các mức security nghiêm ngặt hơn của Tor Browser cũng chặn request này, khiến layout bị lỗi. Hãy self-host mọi asset mà trang cần.

Host header không khớp

Nếu server_name không khớp với header Host mà tor gửi, nginx sẽ fallback về default server của listen address đó. Trên máy chỉ có một vhost, lỗi này không dễ nhận thấy vì server block duy nhất cũng là default. Khi thêm clearnet vhost, các request đến onion có thể bắt đầu rơi vào vhost đó, kèm theo canonical tag và redirect của clearnet site. Chạy lại kiểm tra curl -H 'Host: ...' sau mỗi lần thay đổi nginx, rồi grep kết quả để tìm domain thật của bạn:

curl -s -H 'Host: <your-address>.onion' http://127.0.0.1:8080/ | grep -o 'https\?://[^"]*' | sort -u

Biết process nào sở hữu socket nào là phần lớn công việc này (cách port và listening socket hoạt động trên Linux).

Những gì còn lại trong log

Mọi request đều đến từ 127.0.0.1, nên nginx không có địa chỉ visitor để ghi lại và access_log off; không khiến bạn mất gì. Application phía trên lại là chuyện khác, vì order, email address hoặc metadata của file được upload đều do bạn xử lý. Thói quen của chính bạn cũng quan trọng: quản trị máy qua một login chưa được harden nằm ngoài phạm vi bảo vệ của Tor, nên hãy xem hardening SSH trên cùng VPS là một phần của cấu hình này.

Sao lưu private key vì đó là địa chỉ

/var/lib/tor/onion_site/hs_ed25519_secret_key là service. Không có registrar và không có cơ chế khôi phục. Mất private key thì mất địa chỉ. Nếu sao chép private key, bất kỳ ai giữ bản sao đều có thể chạy nội dung của riêng họ tại địa chỉ đó, và bạn không có cách nào thu hồi quyền.

sudo systemctl stop tor@default
sudo tar -C /var/lib/tor -czf onion-keys.tgz onion_site
sudo chmod 600 onion-keys.tgz
sudo systemctl start tor@default

Mã hóa archive đó (gpg -c onion-keys.tgz) rồi chuyển archive ra khỏi server. Khi restore trên VPS mới, chỉ cần archive và ownership mà tor yêu cầu:

sudo systemctl stop tor@default
sudo tar -C /var/lib/tor -xzf onion-keys.tgz
sudo chown -R debian-tor:debian-tor /var/lib/tor/onion_site
sudo chmod 700 /var/lib/tor/onion_site
sudo systemctl start tor@default
sudo cat /var/lib/tor/onion_site/hostname

Địa chỉ cũ sẽ hoạt động trên phần cứng mới sau một hoặc hai phút, khi tor publish lại descriptor. Toàn bộ quá trình migration chỉ có vậy: không cần thay đổi DNS và không cần cấp lại certificate.

Onion-Location khi website cũng có trên clearnet

Nếu onion chỉ để tiện truy cập thay vì dùng cho mục đích bí mật, hãy công bố nó từ clearnet vhost:

add_header Onion-Location http://<your-address>.onion$request_uri;

Tor Browser sẽ hiển thị nút .onion available trên thanh địa chỉ và đề xuất chuyển sang onion. Header này chỉ được chấp nhận khi trang clearnet được phục vụ qua HTTPS và giá trị của nó là một onion URL hợp lệ.

Có một quy tắc của nginx cần lưu ý. Các directive add_header chỉ được kế thừa bởi block location khi block đó không khai báo directive riêng. Vì vậy, location có add_header riêng sẽ âm thầm bỏ Onion-Location. Hãy lặp lại header ở đó hoặc đặt toàn bộ response header tại một nơi. Việc chủ động công bố header này liên kết hai website với nhau. Đây là cách đúng khi chúng là mirror, nhưng sai với các website cần giữ tách biệt và không liên kết.

Địa chỉ tùy biến

mkp224o tạo các cặp key cho đến khi một cặp tạo ra địa chỉ bắt đầu bằng prefix bạn yêu cầu. Đây là cách tìm kiếm brute force, nên không cần cấu hình gì ngoài prefix và thời gian bạn chấp nhận chờ.

sudo apt install -y git gcc libc6-dev libsodium-dev make autoconf
git clone https://github.com/cathugger/mkp224o
cd mkp224o
./autogen.sh
./configure --enable-amd64-51-30k
make
./mkp224o -d onionkeys blog

Mỗi kết quả tìm được nằm trong onionkeys/<address>.onion/, chứa hostname và hs_ed25519_secret_key. Để cài đặt một kết quả, hãy dừng tor, sao chép thư mục đó vào HiddenServiceDir, rồi áp dụng chown và chmod 700 giống như phần khôi phục ở trên.

Độ dài prefix quyết định toàn bộ chi phí. Địa chỉ dùng base32, nên mỗi ký tự bổ sung sẽ nhân số key dự kiến cần tạo lên 32 lần. Prefix ngắn có thể hoàn tất trên laptop. Prefix dài thì không thể hoàn tất trên bất kỳ thiết bị nào bạn sở hữu. Vanity prefix cũng khiến người đọc quen nhận diện vài ký tự đầu thay vì toàn bộ địa chỉ. Đây là thói quen mà các bản sao phishing của onion site thường lợi dụng.

Các lỗi thường gặp và chuỗi bạn sẽ thấy

Không có tệp hostname sau khi khởi động lại. Tor chưa khởi động hoặc đã từ chối thư mục này. sudo journalctl -u tor@default -n 50 sẽ cho biết nguyên nhân:

/var/lib/tor/onion_site/ is not owned by this user (debian-tor, 108) but by root (0). Perhaps you are running Tor as the wrong user?

Đó là dạng của một thư mục được tạo thủ công. Hãy sửa owner và mode, hoặc xóa thư mục để tor tự tạo lại.

Tor Browser hiển thị Onionsite Not Found (0xF0). Client không lấy được descriptor, nên từ góc nhìn của network, không có gì được publish tại địa chỉ đó. Xác nhận tor đang chạy và đã bootstrap, so sánh địa chỉ bạn nhập với sudo cat /var/lib/tor/onion_site/hostname theo từng ký tự, rồi kiểm tra clock. Tor cần thời gian chính xác để publish và xác thực descriptor, và timedatectl phải báo System clock synchronized: yes.

Địa chỉ phân giải được nhưng trang không bao giờ tải xong. Tor đã hoàn tất rendezvous rồi fail ở hop cuối, từ tor đến nginx. Hop này là local nên log của tor không có thông tin. Chạy curl -sI http://127.0.0.1:8080/ trên server. Connection refused có nghĩa là nginx đã dừng hoặc đang listen trên địa chỉ khác với địa chỉ mà HiddenServicePort trỏ đến.

Trang tải được nhưng mọi liên kết đều trỏ đến domain thật của bạn. Nguyên nhân là các URL tuyệt đối trong template. Chạy kiểm tra grep -o 'https\?://[^"]*' ở trên và sửa mọi nội dung mà lệnh hiển thị trước khi chia sẻ địa chỉ ở bất kỳ đâu.

Hoạt động bình thường nhưng dừng sau khi reboot. Hãy chủ động reboot máy một lần trước khi dựa vào site này, rồi chạy sudo systemctl status tor@default và sudo systemctl status nginx. Một service được ai đó khởi động thủ công trông giống hệt service đã enable cho đến khi máy restart.

FAQ

Tôi có cần mở một cổng trên firewall cho onion service của Tor không?

Không. Tor daemon chỉ tạo các kết nối outbound đến directory server, introduction point và từng rendezvous relay, nên không cần rule inbound; bản thân web server vẫn listen trên 127.0.0.1. Giữ ufw ở chế độ mặc định deny đối với traffic inbound và chỉ cho phép SSH. Đặc tính này cũng có nghĩa là onion service có thể hoạt động trên máy nằm sau NAT (network address translation) mà hoàn toàn không cần public IP.

Tại sao tôi không thể truy cập địa chỉ .onion trong Tor Browser?

Hãy kiểm tra từ server ra ngoài. sudo journalctl -u tor@default -n 50 phải hiển thị Bootstrapped 100% (done): Done, sau đó curl -sI http://127.0.0.1:8080/ trên server phải trả về một status line. Tiếp theo, so sánh địa chỉ bạn đã nhập với file hostname, vì chỉ cần sai một ký tự là thành một service khác. Onionsite Not Found (0xF0) có nghĩa là không tìm thấy descriptor cho địa chỉ đó. Nguyên nhân thường là tor không chạy hoặc system clock không đúng.

Tôi có thể chuyển onion site sang server mới mà vẫn giữ nguyên địa chỉ không?

Có. Địa chỉ được tạo từ hs_ed25519_secret_key, vì vậy hãy copy toàn bộ HiddenServiceDir sang máy mới, đặt ownership thành debian-tor và mode 700, rồi start tor. Địa chỉ sẽ hoạt động lại sau khi descriptor được republish; bạn không cần cập nhật DNS record. Nếu mất file đó thì không thể khôi phục địa chỉ. Vì vậy, hãy backup file bằng mã hóa và lưu bên ngoài server ngay trong ngày tạo file.

Onion site có cần chứng chỉ HTTPS không?

Không. Địa chỉ dài 56 ký tự chính là public key của service, nên kết nối đã được mã hóa và xác thực end-to-end. Tor Browser cũng coi http:// trên hostname .onion là secure context. Dùng lại clearnet certificate trên onion vhost còn tệ hơn không dùng gì, vì Certificate Transparency log là công khai và ghi vĩnh viễn những hostname nào dùng chung một certificate. Lý do duy nhất để mua certificate cho hostname .onion là muốn có sự bảo đảm thương hiệu từ một CA có cấp loại certificate này; mối liên kết đó vốn được công khai theo thiết kế.