Target Server Security Guide
This guide is for the developer who owns a target Linode behind *.eng.sttark.com, such as hvac.eng.sttark.com.
It is written so a human or AI agent can complete the server-side work without using this repository's setup scripts.
Goal
The target server must be reachable like this:
browser or API client -> machine broker -> target server over VPC
The target server must not be directly reachable from the public internet on its app ports.
Target Outcome
When this guide is complete:
https://hvac.eng.sttark.comis protected by broker-side auth- the HVAC app is only reachable from the broker over the private VPC
- direct public access to the target app ports is blocked
- SSH remains available for administration
Current Broker Values
Unless the broker owner tells you otherwise, use these values:
- Broker public IP:
172.238.218.19 - Broker VPC IP:
10.0.1.2 - VPC subnet:
10.0.1.0/24 - Proxy base domain:
eng.sttark.com
What The Target Developer Must Deliver
You are responsible for:
- Putting your Linode in the same VPC as the broker.
- Making your application listen on a private address and port the broker can reach.
- Blocking direct public access to that application.
- Giving the broker owner the final private target URL, for example
http://10.0.1.5:3000. - Verifying the app works correctly when accessed through
https://<subdomain>.eng.sttark.com.
Preconditions
Before you start, you need:
- the Linode API token if you are using the API
- the Linode ID of the target server
- the config profile ID for the target server if modifying network interfaces by API
- the broker owner to confirm the correct subdomain and target port
- shell access to the target machine as
rootor another sudo-capable user
Step 1: Put The Linode On The Broker VPC
The target Linode must be on the same Linode VPC as the broker, with the VPC interface configured as the only interface in the config profile and NAT 1:1 enabled.
Do not keep a separate public interface alongside the VPC interface. That causes routing issues.
Required Linode network shape
Use a config equivalent to:
{
"interfaces": [
{
"purpose": "vpc",
"subnet_id": "<SUBNET_ID>",
"ipv4": { "nat_1_1": "any" }
}
],
"helpers": { "network": true }
}
Manual API workflow
Power the Linode off before changing interfaces:
curl -X POST \
-H "Authorization: Bearer $LINODE_API_KEY" \
"https://api.linode.com/v4/linode/instances/<LINODE_ID>/shutdown"
List config profiles:
curl -s \
-H "Authorization: Bearer $LINODE_API_KEY" \
"https://api.linode.com/v4/linode/instances/<LINODE_ID>/configs" | jq
Update the chosen config profile:
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 }
}'
Boot the Linode again:
curl -X POST \
-H "Authorization: Bearer $LINODE_API_KEY" \
"https://api.linode.com/v4/linode/instances/<LINODE_ID>/boot"
Verify VPC assignment
On the Linode:
ip addr show eth0
ip route
You should see:
- a private address in
10.0.1.0/24 - normal outbound internet access through NAT 1:1
Step 2: Make The App Reachable On The Private Network
Your app must listen on a port that the broker can reach over the VPC.
Good examples:
http://10.0.1.5:3000http://10.0.1.5:8080http://127.0.0.1:3000behind a local reverse proxy that listens on10.0.1.5:80
Requirements
- The app must respond on the target URL you plan to register.
- If the app uses WebSockets, they must work through a reverse proxy.
- The app should tolerate the external hostname
hvac.eng.sttark.com. - The app should prefer relative URLs for assets and API calls.
Quick checks on the target host
Replace the port with your app port:
ss -ltnp | grep ':3000'
curl -I http://127.0.0.1:3000
curl -I http://10.0.1.5:3000
From the broker host, the broker owner should also be able to run:
curl -I http://10.0.1.5:3000
Step 3: Apply Host Firewall Rules On The Target
The target host firewall must be default-deny for inbound traffic.
Allow only:
22/tcpfrom anywhere for SSH- all traffic from
10.0.1.0/24so the broker and other VPC peers can reach the app
For Ubuntu or Debian with UFW:
sudo apt-get update
sudo apt-get install -y ufw fail2ban
sudo ufw --force reset
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp comment 'Allow SSH'
sudo ufw allow from 10.0.1.0/24 comment 'Allow broker and VPC peers'
sudo ufw --force enable
sudo ufw status verbose
Recommended fail2ban setup:
sudo tee /etc/fail2ban/jail.local >/dev/null <<'EOF'
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
EOF
sudo systemctl enable --now fail2ban
sudo systemctl status fail2ban --no-pager
Required firewall result
The server must behave like this:
sshfrom the public internet workscurl http://<public-ip>:<app-port>fails from the public internetcurl http://<vpc-ip>:<app-port>works from the broker or another host on the VPC
Step 4: Apply Linode Cloud Firewall Rules
Host firewall is not enough. The Linode Cloud Firewall should also block direct public app access before traffic reaches the VM.
Create or update a firewall with these inbound rules:
- allow
22/tcpfrom0.0.0.0/0and::/0 - allow
1-65535/tcpfrom172.238.218.19/32 - drop all other inbound traffic
Outbound policy can remain allow.
Example create call
curl -X POST \
-H "Authorization: Bearer $LINODE_API_KEY" \
-H "Content-Type: application/json" \
"https://api.linode.com/v4/networking/firewalls" \
-d '{
"label": "sttark-target-firewall",
"rules": {
"inbound_policy": "DROP",
"outbound_policy": "ACCEPT",
"inbound": [
{
"label": "allow-ssh",
"action": "ACCEPT",
"protocol": "TCP",
"ports": "22",
"addresses": {
"ipv4": ["0.0.0.0/0"],
"ipv6": ["::/0"]
}
},
{
"label": "allow-broker",
"action": "ACCEPT",
"protocol": "TCP",
"ports": "1-65535",
"addresses": {
"ipv4": ["172.238.218.19/32"]
}
}
],
"outbound": []
}
}'
Attach it to the Linode:
curl -X POST \
-H "Authorization: Bearer $LINODE_API_KEY" \
-H "Content-Type: application/json" \
"https://api.linode.com/v4/networking/firewalls/<FIREWALL_ID>/devices" \
-d '{
"type": "linode",
"id": <LINODE_ID>
}'
Step 5: Verify Broker Reachability Over VPC
From the target server:
ping -c 1 10.0.1.2
From the broker, the broker owner should verify your app endpoint:
curl -I http://<target-vpc-ip>:<target-port>
If this fails, do not register the proxy route yet.
Step 6: Hand Off The Final Target URL
When your server is ready, send the broker owner:
- subdomain, for example
hvac - target URL, for example
http://10.0.1.5:3000 - confirmation that public app ports are blocked
- confirmation that broker reachability over VPC was tested
The broker owner will register a route like:
{
"type": "register_proxy",
"subdomain": "hvac",
"target": "http://10.0.1.5:3000"
}
Step 7: Validate End-To-End Behavior
After the route is registered, verify all of these:
Browser path
- visiting
https://hvac.eng.sttark.comredirects to Google sign-in if you are not authenticated - after sign-in with a
@sttark.comaccount, the HVAC app loads
Programmatic path
With a valid broker API key:
curl -I \
-H "X-API-Key: <BROKER_API_KEY>" \
https://hvac.eng.sttark.com/
Expected result:
- the request succeeds
- the app content comes from your HVAC server, not the broker health page
Public-direct path
From a machine outside the VPC:
curl -I http://<public-ip>:<app-port>
curl -I https://<public-ip>:<app-port>
Expected result:
- both direct public checks fail or time out
App Compatibility Notes
Your application should assume it is behind a reverse proxy.
Required behaviors
- respect
Host,X-Forwarded-Host, andX-Forwarded-Proto - support HTTPS at the public edge even if the local target is plain HTTP
- not hardcode the machine broker domain into app redirects
- not require direct public exposure for static assets, APIs, or WebSockets
Common issues
- absolute redirects to the wrong host
- asset URLs pointing at a raw IP or localhost
- WebSocket endpoints hardcoded to local addresses
- CSRF or origin checks that do not allow
https://hvac.eng.sttark.com
AI Agent Success Criteria
If you are giving this document to an AI agent, the task is complete only when all of these are true:
- the Linode is on the broker VPC using a VPC-only interface with NAT 1:1
- the target app is reachable on a private VPC URL
- UFW or equivalent is default-deny, with only SSH and
10.0.1.0/24allowed inbound - Linode Cloud Firewall is attached and only allows SSH plus broker-originated traffic
- direct public access to the app port is blocked
- the broker owner has the final target URL to register
- end-to-end access through
https://<subdomain>.eng.sttark.comworks after registration
Troubleshooting
The server becomes unreachable after adding the VPC
Cause:
- the config profile contains both
publicandvpcinterfaces - or Network Helper is disabled
Fix:
- power off the Linode
- set the VPC interface as the only interface
- enable Network Helper
- boot the Linode again
The broker cannot reach the target over 10.0.1.x
Check:
- both machines are in the same Linode region
- both machines are in the same VPC
- the target firewall allows
10.0.1.0/24 - the app is actually listening on the expected port
The app loads by direct public IP
Your target is not secured yet. Fix one or both of:
- host firewall rules
- Linode Cloud Firewall rules
The app loads through the broker but assets or WebSockets fail
Fix the app to behave correctly behind a reverse proxy:
- use the public hostname
- use forwarded headers
- avoid hardcoded internal URLs
Related Docs
docs/PROXY_INTEGRATION.mddocs/VPC_SETUP.md