VPC Setup Guide
This guide covers setting up Linode VPC (Virtual Private Cloud) networking for secure inter-server communication between Machine Broker infrastructure.
Overview
A VPC provides a private network (10.0.1.0/24) for Linodes in the same region. This allows services to communicate over private IPs instead of the public internet -- useful for:
- Proxy targets pointing to internal services
- Database connections between servers
- Any inter-service traffic that shouldn't traverse the public internet
Critical: VPC Interface Configuration
When adding a Linode to a VPC, the VPC interface must be the only interface configured in the Linode's config profile. Do not include a separate public purpose interface alongside the VPC interface -- this causes a routing conflict that makes the server unreachable.
Instead, enable NAT 1:1 on the VPC interface itself. This gives the Linode both a VPC IP (e.g., 10.0.1.2) and public internet connectivity through the mapped public IP. Linode's Network Helper (which must be enabled) will automatically configure eth0 as the VPC interface with proper routing.
Correct setup:
interfaces: [
{ purpose: "vpc", subnet_id: <ID>, ipv4: { nat_1_1: "any" } }
]
Network Helper: enabled
Wrong setup (causes unreachable server):
interfaces: [
{ purpose: "public" },
{ purpose: "vpc", subnet_id: <ID>, ipv4: { nat_1_1: "any" } }
]
Architecture
┌─────────────────────────────────────────────────┐
│ Linode VPC (us-mia) │
│ 10.0.1.0/24 │
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Machine Broker │ │ Other Service │ │
│ │ Public: x.x.x.x │ │ Public: y.y.y.y │ │
│ │ VPC: 10.0.1.2 │◄─►│ VPC: 10.0.1.3 │ │
│ │ (NAT 1:1 on eth0)│ │ (NAT 1:1 on eth0)│ │
│ └──────────────────┘ └──────────────────┘ │
│ │
└─────────────────────────────────────────────────┘
Prerequisites
- Linode API key (
LINODE_API_KEYin.env) - All Linodes must be in the same region (default:
us-mia) - VPC-capable region (Miami, Chicago, LA, Seattle, or Washington DC)
Step-by-Step Setup
1. Create the VPC
Run the VPC setup script:
# Uses defaults: us-mia region, 10.0.1.0/24 subnet
./deploy/scripts/setup-vpc.sh
# Or specify a different region
VPC_REGION=us-ord ./deploy/scripts/setup-vpc.sh
This creates:
- A VPC named
sttark-vpc - A subnet
sttark-mainwith range10.0.1.0/24 - Saves VPC ID and Subnet ID to
deploy/scripts/vpc_id.txtandvpc_subnet_id.txt
2. Provision a New Linode with VPC
If vpc_subnet_id.txt exists, provision-linode.sh automatically includes the VPC interface:
./deploy/scripts/provision-linode.sh
The Linode will have:
eth0-- VPC interface with NAT 1:1 (provides both VPC IP and public internet access)
3. Add an Existing Linode to the VPC
For Linodes already running, the VPC interface must be set while the Linode is powered off:
# 1. Shut down the Linode
curl -X POST -H "Authorization: Bearer $LINODE_API_KEY" \
"https://api.linode.com/v4/linode/instances/<LINODE_ID>/shutdown"
# 2. Get the config profile ID
curl -s -H "Authorization: Bearer $LINODE_API_KEY" \
"https://api.linode.com/v4/linode/instances/<LINODE_ID>/configs" | jq '.data[0].id'
# 3. Set VPC as the ONLY interface with NAT 1:1 and Network Helper enabled
curl -X PUT -H "Authorization: Bearer $LINODE_API_KEY" \
-H "Content-Type: application/json" \
"https://api.linode.com/v4/linode/instances/<LINODE_ID>/configs/<CONFIG_ID>" \
-d '{
"interfaces": [
{"purpose": "vpc", "subnet_id": <SUBNET_ID>, "ipv4": {"nat_1_1": "any"}}
],
"helpers": {"network": true}
}'
# 4. Boot the Linode
curl -X POST -H "Authorization: Bearer $LINODE_API_KEY" \
"https://api.linode.com/v4/linode/instances/<LINODE_ID>/boot"
4. Find the VPC IP
After the Linode boots with a VPC interface, find its assigned VPC IP:
# Via Linode API
curl -s -H "Authorization: Bearer $LINODE_API_KEY" \
"https://api.linode.com/v4/linode/instances/<LINODE_ID>/configs" | \
jq '.data[].interfaces[] | select(.purpose=="vpc") | .ipv4'
# Or SSH into the Linode and check
ip addr show eth0
5. Configure with Ansible
Update your inventory.ini to enable VPC configuration:
[machinebroker]
machinebroker.sttark.com ansible_host=172.238.218.19 ansible_user=root
[machinebroker:vars]
ansible_python_interpreter=/usr/bin/python3
vpc_enabled=true
vpc_ip=10.0.1.2
Then run the playbook:
cd deploy/ansible
ansible-playbook -i inventory.ini site.yml
Ansible will:
- Add a UFW firewall rule allowing traffic from the VPC subnet (
10.0.1.0/24) - Verify and display the VPC network configuration
No manual interface configuration is needed -- Linode's Network Helper handles eth0 setup automatically.
6. Verify VPC Connectivity
SSH into the Linode and test:
# Check that eth0 has the VPC IP
ip addr show eth0
# Verify public internet works (via NAT 1:1)
curl -s https://ifconfig.me
# Ping another Linode on the VPC (if one exists)
ping 10.0.1.3
Using VPC IPs with the Proxy
Once VPC is configured, you can register proxy routes that point to VPC IPs instead of public IPs. This keeps traffic on the private network:
{
"type": "register_proxy",
"subdomain": "internal-app",
"target": "http://10.0.1.3:8080"
}
Traffic flow: https://internal-app.eng.sttark.com -> Machine Broker -> 10.0.1.3:8080 (private VPC)
Configuration Reference
Ansible Variables (group_vars/all.yml)
| Variable | Default | Description |
|---|---|---|
vpc_enabled |
false |
Enable VPC firewall rules |
vpc_ip |
"" |
VPC IP assigned by Linode (e.g., 10.0.1.2) |
vpc_subnet |
10.0.1.0 |
VPC subnet address |
Inventory Variables (per-host override)
These can be set per-host in inventory.ini to override defaults:
[machinebroker]
broker1 ansible_host=172.x.x.x vpc_enabled=true vpc_ip=10.0.1.2
broker2 ansible_host=172.y.y.y vpc_enabled=true vpc_ip=10.0.1.3
Environment Variables (for scripts)
| Variable | Default | Description |
|---|---|---|
VPC_REGION |
us-mia |
Linode region for VPC |
VPC_LABEL |
sttark-vpc |
VPC label in Linode |
LINODE_API_KEY |
(from .env) |
Linode API token |
VPC-Capable Regions
| Region ID | Location | VPC Support |
|---|---|---|
us-mia |
Miami, FL | Yes |
us-ord |
Chicago, IL | Yes |
us-iad |
Washington, DC | Yes |
us-lax |
Los Angeles, CA | Yes |
us-sea |
Seattle, WA | Yes |
us-southeast |
Atlanta, GA | No |
us-central |
Dallas, TX | No |
us-east |
Newark, NJ | No |
All Linodes in a VPC must be in the same region.
Troubleshooting
Server unreachable after adding VPC interface
Symptom: After adding a VPC interface and rebooting, the server is unreachable (no SSH, no ping, no HTTPS).
Cause: A public purpose interface was included alongside the VPC interface, creating a routing conflict. Or Network Helper was disabled.
Fix:
- Power off the Linode via the API:
curl -X POST -H "Authorization: Bearer $LINODE_API_KEY" \ "https://api.linode.com/v4/linode/instances/<ID>/shutdown" - Set the VPC interface as the only interface with NAT 1:1 and Network Helper enabled:
curl -X PUT -H "Authorization: Bearer $LINODE_API_KEY" \ -H "Content-Type: application/json" \ "https://api.linode.com/v4/linode/instances/<ID>/configs/<CONFIG_ID>" \ -d '{ "interfaces": [ {"purpose": "vpc", "subnet_id": <SUBNET_ID>, "ipv4": {"nat_1_1": "any"}} ], "helpers": {"network": true} }' - Boot the Linode:
curl -X POST -H "Authorization: Bearer $LINODE_API_KEY" \ "https://api.linode.com/v4/linode/instances/<ID>/boot"
If you need to temporarily remove VPC to restore connectivity, set interfaces to just [{"purpose": "public"}] while the Linode is powered off.
UFW blocking VPC traffic
If VPC-connected Linodes can't reach each other, ensure the firewall allows VPC subnet traffic. Ansible handles this automatically when vpc_enabled=true, but to add it manually:
ufw allow from 10.0.1.0/24 comment "Allow VPC internal traffic"
Linode not in a VPC-capable region
VPC is only available in certain Linode regions. If you get an error creating a VPC, check the region supports it. Use this command to list VPC-capable regions:
curl -s -H "Authorization: Bearer $LINODE_API_KEY" \
"https://api.linode.com/v4/regions" | \
jq '.data[] | select(.capabilities[] == "VPCs") | {id, label}'