Ansible playbook for provisioning a two-server chained proxy setup:
Client-side Surge uses underlying-proxy to chain connections:
Client → Relay (SS) → Landing (Trojan) → Internet # chained
Client → Landing (Trojan) → Internet # direct
Client → Relay (SS) → Internet # relay only
community.general Ansible collection: ansible-galaxy collection install community.generalcp inventory/hosts.yml.example inventory/hosts.yml
Edit inventory/hosts.yml with your server IPs and SSH user:
all:
children:
relay:
hosts:
relay-server:
ansible_host: "1.2.3.4"
ansible_user: "ubuntu" # or "root"
landing:
hosts:
landing-server:
ansible_host: "5.6.7.8"
ansible_user: "ubuntu" # or "root"
SSH user options:
ansible_user: "root" — connects directly as rootansible_user: "ubuntu" (or any user) — requires passwordless sudo on the server. If sudo requires a password, add --ask-become-pass when running the playbookMake sure your SSH key is already configured for the specified user on both servers.
for f in group_vars/*.yml.example; do cp "$f" "${f%.example}"; done
Edit group_vars/landing.yml:
trojan_domain: Your domain namecertbot_email: Email for Let's Encrypt notificationsPorts and passwords are auto-generated on first run and persisted in credentials/. No manual password setup needed.
To override auto-generated values:
ansible-playbook site.yml --extra-vars "ss_password=my-custom-pass ss_port=12345"
ansible-playbook site.yml
After deployment, the Surge client config is generated at output/surge-client.conf with all connection parameters filled in. Import this file into Surge directly.
The credentials/ directory contains auto-generated passwords and ports. Back it up — if lost, new credentials will be generated and servers must be re-provisioned.
cp -r credentials/ /path/to/backup/
├── ansible.cfg
├── inventory/
│ └── hosts.yml
├── group_vars/
│ ├── all.yml
│ ├── relay.yml
│ └── landing.yml
├── roles/
│ ├── base/ # SSH hardening, UFW, fail2ban
│ ├── geoblock/ # Block outbound to China IPs
│ ├── shadowsocks/ # shadowsocks-rust (relay)
│ └── trojan/ # trojan-go + certbot (landing)
├── templates/
│ └── surge-client.conf.j2
├── credentials/ # Auto-generated (gitignored)
├── output/ # Generated Surge config (gitignored)
└── site.yml
| Variable | Default | Description |
|---|---|---|
ss_port |
auto-generated (10000–49999) | Shadowsocks listen port |
ss_cipher |
aes-256-gcm | Shadowsocks encryption method |
ss_password |
auto-generated (32 chars) | Shadowsocks password |
ss_version |
1.21.2 | shadowsocks-rust release version |
trojan_port |
443 (fixed) | Trojan listen port |
trojan_domain |
— | Domain name for TLS certificate |
trojan_password |
auto-generated (32 chars) | Trojan password |
trojan_fallback_port |
8080 | Fallback port for non-Trojan traffic |
trojan_version |
0.10.6 | trojan-go release version |
certbot_email |
— | Email for Let's Encrypt |
ssh_port |
22 | SSH listen port |