VPN & Internal Tools
Production AWS resources — Aurora, Redis, ClickHouse, Bull Studio, and RedisInsight — all live in private VPC subnets with no public internet access. WireGuard VPN tunnels your traffic through the Zelly bastion EC2 into the production VPC. Once connected, every private resource is reachable by its VPC private IP — no per-port SSH tunnels needed for Redis, Aurora, or ClickHouse.
Staging Aurora is publicly accessible so VPN is not required for staging database access. For everything else in production, connect first.
What lives behind the VPN
| Resource | How to find the address | Port | Notes |
|---|---|---|---|
| Aurora MySQL | Secrets Manager: zelly/aurora/master → .host | 3306 | Prod only — staging is public |
| ElastiCache Redis | Secrets Manager: zelly/redis/auth → .host | 6379 TLS | Both envs; TLS required |
| ClickHouse | Secrets Manager: zelly/clickhouse/env → .host | 8123 HTTP · 9000 native | EC2 in private subnet |
| Bull Studio | ECS task private IP — see below | 3000 | Direct browser access via VPN |
| RedisInsight | ECS task private IP — see below | 5540 | Direct browser access via VPN |
http://zelly-bull.test and RedisInsight at http://zelly-redis.test via the docker-compose stack. See Local Development.Step 1 — Install WireGuard
Do this once per machine. Pick your OS:
- Go to wireguard.com/install and download the Windows installer (.msi).
- Run the installer — no custom options needed. WireGuard appears in the system tray when running.
- Open a new PowerShell or Command Prompt window after install and verify:
wg / wg-quick CLI tools. The GUI is easiest for first-time setup; the CLI is useful in scripts.Option A — GUI app (easiest)
- Open the Mac App Store and search for WireGuard (free, by WireGuard Development Team).
- Install it. The app appears in your Applications folder and adds a menu-bar icon.
Option B — CLI via Homebrew
brew install wireguard-tools
wg --version # verifysudo apt update && sudo apt install -y wireguard wireguard-tools wg --version
sudo dnf install -y wireguard-tools wg --version
sudo pacman -S wireguard-tools
Step 2 — Create your VPN configuration
Each developer has a unique key pair and an assigned VPN IP in the 10.10.0.0/24 subnet. You generate your own private key — it never leaves your machine.
-
Generate a key pairbash
wg genkey | tee private.key | wg pubkey > public.key echo "Private key (keep secret):" && cat private.key echo "" echo "Public key (share with DevOps):" && cat public.key
PowerShell — use Git Bash or WSL for key generation# Open Git Bash (ships with Git for Windows) or WSL and run: wg genkey | tee private.key | wg pubkey > public.key cat private.key # goes in your config file — never share cat public.key # share this with DevOps
Never share your private key. If it leaks, revoke it immediately — see Troubleshooting for the revocation steps. -
Get the bastion's server public key and public IP
The bastion stores its own public key in SSM Parameter Store on first boot. Retrieve it:
bash — production (ap-south-1)# Server public key SERVER_PUBKEY=$(aws ssm get-parameter \ --name /zelly/bastion/wireguard-public-key \ --region ap-south-1 \ --query Parameter.Value --output text) # Bastion public IP BASTION_IP=$(aws ec2 describe-instances \ --filters "Name=tag:Name,Values=zelly-production-bastion" \ "Name=instance-state-name,Values=running" \ --region ap-south-1 \ --query 'Reservations[0].Instances[0].PublicIpAddress' --output text) echo "Server public key: $SERVER_PUBKEY" echo "Bastion IP: $BASTION_IP"
PowerShell — production$SERVER_PUBKEY = aws ssm get-parameter ` --name /zelly/bastion/wireguard-public-key ` --region ap-south-1 ` --query Parameter.Value --output text $BASTION_IP = aws ec2 describe-instances ` --filters "Name=tag:Name,Values=zelly-production-bastion" ` "Name=instance-state-name,Values=running" ` --region ap-south-1 ` --query "Reservations[0].Instances[0].PublicIpAddress" --output text Write-Host "Server public key: $SERVER_PUBKEY" Write-Host "Bastion IP: $BASTION_IP" -
Register your public key
Share your public key (from
public.key) and your name with a team lead. You'll be assigned a VPN IP — for example,10.10.0.3. The bastion config is updated via SSM RunCommand (no SSH required). -
Write your config file
Create
zelly-prod.conf(anywhere on your machine). Fill in the three placeholders:zelly-prod.conf[Interface] PrivateKey = <paste your private.key contents here> Address = 10.10.0.<YOUR_ASSIGNED_NUMBER>/32 [Peer] # Zelly production bastion PublicKey = <server public key from Step 2> Endpoint = <bastion public IP>:51820 AllowedIPs = 10.0.0.0/8 PersistentKeepalive = 25
AllowedIPs = 10.0.0.0/8is a split tunnel — only10.x.x.xtraffic routes through the VPN. Your regular internet connection is unaffected. The VPC production CIDR is10.0.0.0/16and staging is10.1.0.0/16— both are covered by10.0.0.0/8.
Step 3 — Connect to the VPN
- Open the WireGuard app (system tray or Start menu).
- Click Import tunnel(s) from file → select
zelly-prod.conf. - Click Activate. The status dot turns green when connected.
- Test:
- Open WireGuard.app from Applications.
- Click Import Tunnel(s) from File… → select
zelly-prod.conf. - Click Activate. The menu-bar icon turns solid.
- Test:
# Connect (sudo required) sudo wg-quick up ~/zelly-prod.conf # Check handshake — confirm a recent handshake time sudo wg show # Test VPC connectivity ping 10.10.0.1 # bastion VPN interface # Disconnect when done sudo wg-quick down ~/zelly-prod.conf
/etc/wireguard/zelly-prod.conf (chmod 600) and then you can use just sudo wg-quick up zelly-prod without the full path.Step 4 — Access resources
Keep the VPN active for all commands in this section.
Redis (ElastiCache)
Production ElastiCache requires TLS — plain-text connections are rejected. Get credentials from Secrets Manager, then connect:
# Pull credentials SECRET=$(aws secretsmanager get-secret-value \ --secret-id zelly/redis/auth --region ap-south-1 \ --query SecretString --output text) HOST=$(echo "$SECRET" | jq -r .host) TOKEN=$(echo "$SECRET" | jq -r .auth_token) # Interactive CLI session redis-cli -h "$HOST" -p 6379 --tls --no-auth-warning -a "$TOKEN" # One-shot health check redis-cli -h "$HOST" -p 6379 --tls -a "$TOKEN" PING # Inspect BullMQ queues redis-cli -h "$HOST" -p 6379 --tls -a "$TOKEN" KEYS "bull:*" | head -20 redis-cli -h "$HOST" -p 6379 --tls -a "$TOKEN" LLEN "bull:store-events:wait" redis-cli -h "$HOST" -p 6379 --tls -a "$TOKEN" LLEN "bull:SHOPIFY_WEBHOOK:wait"
$r = aws secretsmanager get-secret-value ` --secret-id zelly/redis/auth --region ap-south-1 ` --query SecretString --output text | ConvertFrom-Json # Connect with redis-cli (install via choco install redis-desktop-manager or WSL) redis-cli -h $r.host -p 6379 --tls --no-auth-warning -a $r.auth_token # Or display for use in a GUI client like RedisInsight Write-Host "Host: $($r.host)" Write-Host "Port: 6379 (TLS on)" Write-Host "Token: $($r.auth_token)"
--tls. ElastiCache enforces in-transit encryption — omitting --tls returns a protocol error, not a connection timeout.Aurora MySQL (production)
Staging Aurora is publicly accessible — no VPN needed. For production, connect via VPN first.
| Staging | Production | |
|---|---|---|
| VPN required? | No — public endpoint | Yes — private subnet |
| Credentials | zelly/aurora/master (ap-southeast-1) | zelly/aurora/master (ap-south-1) |
| User | zellymaster | |
| Databases | astro_primary, ecom_store_front, backoffice | |
SECRET=$(aws secretsmanager get-secret-value \ --secret-id zelly/aurora/master --region ap-south-1 \ --query SecretString --output text) HOST=$(echo "$SECRET" | jq -r .host) PASS=$(echo "$SECRET" | jq -r .password) mysql -h "$HOST" -u zellymaster -p"$PASS" astro_primary
SECRET=$(aws secretsmanager get-secret-value \ --secret-id zelly/aurora/master --region ap-southeast-1 \ --query SecretString --output text) HOST=$(echo "$SECRET" | jq -r .host) PASS=$(echo "$SECRET" | jq -r .password) mysql -h "$HOST" -u zellymaster -p"$PASS" astro_primary
$s = aws secretsmanager get-secret-value `
--secret-id zelly/aurora/master --region ap-south-1 `
--query SecretString --output text | ConvertFrom-Json
Write-Host "Host: $($s.host)"
Write-Host "User: zellymaster"
Write-Host "Pass: $($s.password)"
# Paste these into MySQL Workbench, TablePlus, or your preferred GUI clientClickHouse
ClickHouse runs on an EC2 instance (t3.medium, Docker) in the private subnet. The host is stored in the zelly/clickhouse/env secret. ClickHouse exposes two interfaces — HTTP (port 8123) and the native TCP protocol (port 9000).
CH=$(aws secretsmanager get-secret-value \ --secret-id zelly/clickhouse/env --region ap-south-1 \ --query SecretString --output text) CH_HOST=$(echo "$CH" | jq -r .host) CH_PASS=$(echo "$CH" | jq -r .password) # Ping the HTTP interface — should return "Ok." curl "http://$CH_HOST:8123/ping" # Run a query via HTTP (quickest for one-off checks) curl "http://$CH_HOST:8123/?user=default&password=$CH_PASS" \ --data "SELECT version()" # Show databases curl "http://$CH_HOST:8123/?user=default&password=$CH_PASS" \ --data "SHOW DATABASES FORMAT Pretty" # Interactive shell via native protocol (install clickhouse-client first) clickhouse-client --host "$CH_HOST" --password "$CH_PASS" --port 9000
$ch = aws secretsmanager get-secret-value ` --secret-id zelly/clickhouse/env --region ap-south-1 ` --query SecretString --output text | ConvertFrom-Json # Ping Invoke-RestMethod "http://$($ch.host):8123/ping" # Query via HTTP API Invoke-RestMethod "http://$($ch.host):8123/?user=default&password=$($ch.password)" ` -Method Post -Body "SELECT version()" # Show databases Invoke-RestMethod "http://$($ch.host):8123/?user=default&password=$($ch.password)" ` -Method Post -Body "SHOW DATABASES"
Internal tools — direct VPN access
Bull Studio and RedisInsight are ECS Fargate tasks in private subnets. Their security group allows inbound on ports 3000 and 5540 from the bastion security group — WireGuard traffic is masqueraded through the bastion's private IP, so once the VPN is connected you can open these tools directly in a browser.
Bull Studio (BullMQ queue dashboard)
Shows all BullMQ queues — job counts, active workers, failed jobs, and retry history. Runs on port 3000.
-
Connect WireGuard (Step 3 above — must be active).
-
Look up the task's private IPbash — production
TASK=$(aws ecs list-tasks \ --cluster zelly-production \ --service-name bull-studio \ --region ap-south-1 \ --query 'taskArns[0]' --output text) aws ecs describe-tasks \ --cluster zelly-production \ --tasks "$TASK" \ --region ap-south-1 \ --query 'tasks[0].attachments[0].details[?name==`privateIPv4Address`].value' \ --output text
PowerShell — production$TASK = aws ecs list-tasks ` --cluster zelly-production --service-name bull-studio ` --region ap-south-1 --query "taskArns[0]" --output text aws ecs describe-tasks ` --cluster zelly-production --tasks $TASK ` --region ap-south-1 ` --query "tasks[0].attachments[0].details[?name=='privateIPv4Address'].value" ` --output text
-
Open in your browser
Navigate to
http://<task-private-ip>:3000— you'll see all BullMQ queues with live job counts. For example:http://10.0.2.47:3000
RedisInsight (Redis GUI)
Visual key browser, stream viewer, and memory analysis for ElastiCache. Runs on port 5540. The Redis connection is pre-configured via REDIS_HOSTS — no manual setup in the UI.
-
Connect WireGuard (must be active).
-
Look up the task's private IPbash — production
TASK=$(aws ecs list-tasks \ --cluster zelly-production \ --service-name redis-insight \ --region ap-south-1 \ --query 'taskArns[0]' --output text) aws ecs describe-tasks \ --cluster zelly-production \ --tasks "$TASK" \ --region ap-south-1 \ --query 'tasks[0].attachments[0].details[?name==`privateIPv4Address`].value' \ --output text
PowerShell$TASK = aws ecs list-tasks ` --cluster zelly-production --service-name redis-insight ` --region ap-south-1 --query "taskArns[0]" --output text aws ecs describe-tasks ` --cluster zelly-production --tasks $TASK ` --region ap-south-1 ` --query "tasks[0].attachments[0].details[?name=='privateIPv4Address'].value" ` --output text
-
Open in your browser
Navigate to
http://<task-private-ip>:5540. The ElastiCache database will already appear in the sidebar — click it to start browsing keys.
Look up all internal tool IPs at once
for SVC in bull-studio redis-insight; do
TASK=$(aws ecs list-tasks --cluster zelly-production --service-name $SVC \
--region ap-south-1 --query 'taskArns[0]' --output text)
IP=$(aws ecs describe-tasks --cluster zelly-production --tasks "$TASK" \
--region ap-south-1 \
--query 'tasks[0].attachments[0].details[?name==`privateIPv4Address`].value' \
--output text)
echo "$SVC → $IP"
done| Tool | URL pattern (VPN connected) | What it shows |
|---|---|---|
| Bull Studio | http://<task-ip>:3000 | BullMQ queues — job counts, retries, failed jobs |
| RedisInsight | http://<task-ip>:5540 | Redis key browser, streams, memory usage |
Troubleshooting
VPN activates but ping 10.10.0.1 gets no reply
1. Check that the handshake happened:
No handshake means the bastion can't be reached on UDP 51820. Your public IP may not be in the bastion's security group allowlist. Contact DevOps to add your current public IP (curl ifconfig.me) to the bastion SG.
2. Check your AllowedIPs: it must include 10.0.0.0/8 or specifically 10.10.0.0/24 to route traffic to the bastion's VPN address.
VPN connected, ping works, but Redis / Aurora is unreachable
Your AllowedIPs may only cover 10.10.0.0/24 (the VPN subnet) instead of 10.0.0.0/8 (the full private range). Edit the conf file, set AllowedIPs = 10.0.0.0/8, then deactivate and re-activate the tunnel.
Also check that you are using the correct secret region: production secrets are in ap-south-1, staging in ap-southeast-1.
redis-cli error: ERR This instance has TLS enabled
You connected without --tls. Always include it:
redis-cli: SSL_connect returned=1 (TLS build not supported)
Your redis-cli was compiled without OpenSSL. Reinstall from a TLS-enabled package:
Browser can't reach Bull Studio or RedisInsight IP
1. Is WireGuard connected? The VPN must be active. Check the app status or run sudo wg show — look for a recent latest handshake time.
2. Did the task restart? Fargate tasks get a new private IP on every restart. Re-run the IP lookup and use the new address.
3. Is the task running?
If running = 0, force a redeploy: aws ecs update-service --cluster zelly-production --service bull-studio --region ap-south-1 --force-new-deployment
4. Terraform not applied yet? The SG ingress rules for ports 3000 and 5540 live in modules/security_groups/main.tf. If not yet applied, run terraform apply -target module.security_groups — SG changes take effect immediately, no task restart needed.
My private key was compromised — how do I revoke it?
- Deactivate the tunnel on your machine immediately.
- DevOps dashboard → VPN → find your entry → click Remove.
- Click Sync to Bastion — this removes your public key from
/etc/wireguard/wg0.confon the bastion via SSM RunCommand. No SSH access to the bastion is needed. - Generate a new key pair and register the new public key as a fresh entry.