Wildtunnel
This tool makes a home-network service available at <name>.<domain>. It does not open an inbound port at home.
Browser -> DNS (Cloudflare) -> VPS (Caddy + frps) -> tunnel -> home PC (frpc) -> your app
Wildtunnel puts the tunnel server on a VPS. It supports two providers: AWS EC2 (deployed via CDK) and AWS Lightsail (deployed via direct API calls, no CDK toolchain needed). The code separates the generic server setup (frps + Caddy) from each provider's deploy logic, so adding another provider needs no rewrite.
Setup and usage
Step 1: Install the CLI
Install frpc on a different machine first: github.com/fatedier/frp/releases
# For the AWS EC2 provider:
uv tool install 'wildtunnel[aws-ec2]'
# For the AWS Lightsail provider (no extra needed, no CDK toolchain):
uv tool install wildtunnel
Tab completion for subcommands, flags, and tunnel names (frpc del <tab>) is available for bash, zsh, and fish. Add the line for your shell to its rc file, then restart your shell:
source <(wildtunnel completion bash) # ~/.bashrc
source <(wildtunnel completion zsh) # ~/.zshrc
wildtunnel completion fish | source # ~/.config/fish/config.fish
Step 2: Deploy the server
Pick one provider.
Step 2a: Deploy to AWS EC2
Creates an EC2 host, frps, Caddy, an Elastic IP, and an IAM role, via CDK.
wildtunnel aws-ec2 config --domain example.com # reads the region from your AWS CLI config
wildtunnel aws-ec2 deploy
Options for aws-ec2 config:
--domain: base domain for the tunnel subdomains, e.g.example.com.--region: AWS region for the stack. Defaults to your AWS CLI region.--extra-port-range START-END: opens a TCP+UDP port range on the EC2 host and in frps, forfrpc add --proto tcp|udp --remote-port. Not opened unless given. Runaws-ec2 deployagain to apply a change.--yes: skips confirmation prompts.
Record the ElasticIp output. Check status anytime with wildtunnel aws-ec2 status.
Get the frpc client's data (server address, base domain, token, and a ready frpc config command):
wildtunnel aws-ec2 info
Step 2b: Deploy to AWS Lightsail
Creates a nano_3_0 Lightsail instance, frps, Caddy, and a static IP, via direct Lightsail API calls. No CDK, no CloudFormation.
wildtunnel aws-lightsail config --domain example.com # reads the region from your AWS CLI config
wildtunnel aws-lightsail deploy
aws-lightsail config takes the same options as aws-ec2 config: --domain, --region, --extra-port-range, --yes. Check status anytime with wildtunnel aws-lightsail status.
Lightsail instances cannot have an IAM role, so wildtunnel writes the frp token directly into the instance's user data at deploy time, instead of fetching it from SSM at boot. One effect: wildtunnel aws-lightsail info only works on the machine that ran deploy, since the token is never stored remotely.
wildtunnel aws-lightsail info
Re-running deploy on a live instance only syncs the static IP and firewall rules (for example after changing --extra-port-range) — Lightsail cannot re-run cloud-init, so it never reinstalls frps/Caddy. Use wildtunnel aws-lightsail diff to check for firewall drift, or destroy + deploy to force a clean reinstall.
Step 3: Point your DNS at the server address
Get the server address: the ElasticIp from aws-ec2 info, or the static IP from aws-lightsail info.
At your DNS provider (for example Cloudflare), add a wildcard A record, *.example.com, pointed at that address. Keep it DNS-only — do not enable the Cloudflare proxy (grey cloud, not orange). Make sure port 80 is open: Caddy needs it to get a TLS certificate per host on demand, via Let's Encrypt HTTP-01.
Step 4: Configure the frpc client
Step 4.1: Add a service
frpc does not read the deployed server automatically. Copy the values, or the ready command, from aws-ec2 info / aws-lightsail info.
wildtunnel frpc config \
--server-addr 1.2.3.4 \
--base-domain example.com \
--token <token>
wildtunnel frpc add openwebui 192.168.50.10:8080
# -> https://openwebui.example.com
frpc config options --server-addr, --base-domain, and --token are all required.
frpc add <name> <local-ip>:<local-port> adds a tunnel for a local service — HTTP/HTTPS at <name>.<base-domain> by default. For a non-HTTP service, for example WireGuard, add --proto tcp or --proto udp with --remote-port <port> (must be in the range you set with --extra-port-range):
frpc add wireguard 127.0.0.1:51820 --proto udp --remote-port 51820
frpc list shows configured tunnels. frpc del <name> removes one.
Step 4.2: Run frpc on a different machine
Use the same data as step 4.1. The render command writes an frpc.toml to stdout, without saving a file or starting a service:
wildtunnel frpc render \
--server-addr 1.2.3.4 \
--base-domain example.com \
--token <token>
Upgrade from aws-reverse-tunnel
This project was renamed from aws-reverse-tunnel to wildtunnel. There is no automatic migration. To keep your existing configuration, move the config directory by hand:
mv ~/.config/aws-reverse-tunnel ~/.config/wildtunnel
Cost
AWS EC2
Runs one t4g.nano EC2 host, an 8 GiB gp3 EBS volume, and one Elastic IP. On-demand list prices below for a 730-hour month; treat as estimates, since AWS prices change.
| Resource | ap-northeast-1 (Tokyo) | us-east-1 |
|---|---|---|
| t4g.nano (730 h) | ~$3.87 | ~$3.07 |
| EBS gp3, 8 GiB | ~$0.77 | ~$0.64 |
| Public IPv4 (Elastic IP) | ~$3.65 | ~$3.65 |
| SSM parameter and KMS | free | free |
| Total (idle) | ~$8.30 | ~$7.35 |
- AWS charges $0.005/hour per public IPv4 address since February 2024 — this applies to the Elastic IP even when idle, making it the largest cost after the host.
- The table excludes data transfer: egress scales with traffic. 100 GB/month is free, then AWS charges about $0.114/GB in Tokyo and about $0.09/GB in us-east-1.
- The SSM parameter uses the AWS-managed KMS key, which has no monthly fee.
- A one-year Compute Savings Plan or Reserved Instance lowers the host cost by about 30–40%.
AWS Lightsail
The nano_3_0 bundle is a flat $5/month (check aws.amazon.com/lightsail/pricing for current prices) — includes the instance, SSD storage, and a monthly data-transfer allowance. No separate EBS or per-GB egress charges like EC2, and no $0.005/hour IP surcharge (a static IP is free while attached to a running instance). Simpler pricing, at the cost of a fixed 0.5 GiB RAM (same as t4g.nano) and no Savings Plan or Reserved Instance discount.
Development
uv sync --all-extras
uv run pytest
uv run mypy src tests
uv run pre-commit run --all-files
Code layout
src/wildtunnel/: the CLI and the generic server installer (server/assets/server-setup.sh), which reads its input from environment variables and makes no AWS calls — so it also runs by hand on any Linux VPS.src/wildtunnel/providers/aws_ec2/: all AWS EC2 code — the CDK stack, app entry point, SSM token helpers, and theaws-ec2CLI group. Includes the stack itself, soaws-ec2 deployneeds no clone of this repo.src/wildtunnel/providers/aws_lightsail/: all AWS Lightsail code — theaws-lightsailCLI group and direct boto3 calls. No CDK: Lightsail has no useful CDK L2 constructs, and the point of Lightsail is to be simpler than EC2+CloudFormation.
Add a new provider
scripts/new_provider.py is a maintainer-only scaffold, not shipped in the package and not wired into the CLI. It generates providers/<name>/ and tests/providers/<name>/, mirroring providers/aws_ec2/'s shape (a Click group, a config dataclass, matching test stubs), then prints the remaining manual steps. Refuses to run if providers/<name>/ already exists.
uv run python scripts/new_provider.py hetzner
Known limitations
- The CDK app creates the frp token, so the plaintext token ends up in the generated CloudFormation template. AWS stores it as a SecureString, but a person with read access to the stack can still see it. Acceptable for this personal, single-account setup.
- Lightsail instances cannot have an IAM role, so wildtunnel writes the frp token directly into the instance's user data instead of fetching it from SSM. A person who can call
lightsail:GetInstancecan see it there. Also,wildtunnel aws-lightsail infoonly works on the machine that randeploy, since the token is never stored remotely.
License
MIT. See LICENSE.
Metadata
Release files for wildtunnel 0.2.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| wildtunnel-0.2.3.tar.gz | 169.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| wildtunnel-0.2.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 218.6 kB
Release files / wildtunnel-0.2.3.tar.gz
| Download URL | wildtunnel-0.2.3.tar.gz |
|---|---|
| Size | 169.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
98405eede8338902a18c811bfab9a9406dd2db592db1abb2842b0471b89407fa
|
|
BLAKE2b-256 checksum How to use checksums |
6306d2a10ed5f7c466f21c4cd530b3229fa9b02146b40d2ce6e249ed6f8a1ecb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / wildtunnel-0.2.3-py3-none-any.whl
| Download URL | wildtunnel-0.2.3-py3-none-any.whl |
|---|---|
| Size | 48.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1c40c004b24e279f9e2a3ea7d4d939abeed96421ea7b248a8760cb231b4b7c32
|
|
BLAKE2b-256 checksum How to use checksums |
afd087f7fac5202aaf159b31f827b5732b5feb8dc2ca8c1b7cd8f360b8688444
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|