Hatch includes a built-in reverse proxy that routes HTTP traffic from subdomains to VMs. This enables clean URLs like https://my-app.hatch.example.com instead of IP:port combinations.
How the Reverse Proxy Works
The proxy uses subdomain-based routing:
Client makes request
A request arrives at https://my-app.hatch.example.com
Proxy extracts subdomain
The proxy extracts my-app from the Host header.
Route lookup
The proxy queries the database for a route matching subdomain my-app.
VM state check
If the target VM is snapshotted and auto_wake: true, the VM is automatically restored.
Request forwarded
Once the VM is running, the request is proxied to http://{guest_ip}:{target_port}.
Creating a Proxy Route
Create a route for a running VM:
curl -X POST https://api.hatch.example.com/vms/vm-abc123/routes \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"subdomain": "my-app",
"target_port": 8080
}'
Response:
{
"id" : "rt-xyz789" ,
"vm_id" : "vm-abc123" ,
"subdomain" : "my-app" ,
"target_port" : 8080 ,
"auto_wake" : true ,
"created_at" : "2026-03-06T14:30:00Z" ,
"updated_at" : "2026-03-06T14:30:00Z"
}
Now HTTP requests to https://my-app.hatch.example.com will be forwarded to the VM’s port 8080.
If you omit the subdomain field, Hatch generates a random 12-character subdomain automatically.
Subdomain Patterns
Valid Subdomains
Subdomains must follow these rules:
Lowercase alphanumeric characters and hyphens
Must start and end with alphanumeric (not hyphen)
Maximum 63 characters
Cannot be a reserved subdomain
Valid examples
Invalid examples
"my-app" ✓
"prod-api" ✓
"web-1" ✓
"staging" ✓
"app-2024-v2" ✓
Reserved Subdomains
These subdomains are reserved and cannot be used:
api, www, admin, app, dashboard, mail, smtp, ftp, ssh,
ns1, ns2, status, docs, help, support, billing, login,
auth, oauth, cdn, static, assets, media, hatch, console,
panel, grafana, prometheus, traefik, minio, postgres,
db, redis, internal
Error when using reserved subdomain:
{
"error" : "subdomain \" api \" is reserved"
}
Random Subdomains
Omit the subdomain field to get an auto-generated unique subdomain:
curl -X POST https://api.hatch.example.com/vms/vm-abc123/routes \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"target_port": 3000
}'
Response with random subdomain:
{
"id" : "rt-xyz789" ,
"subdomain" : "x7k9m2p5n8q1" ,
"target_port" : 3000 ,
"auto_wake" : true
}
Auto-Wake Configuration
The auto_wake field controls whether snapshotted VMs are automatically restored on incoming requests.
Enabled (default)
curl -X POST https://api.hatch.example.com/vms/vm-abc123/routes \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"subdomain": "my-app",
"target_port": 8080,
"auto_wake": true
}'
Behavior:
Request arrives for my-app.hatch.example.com
VM is in snapshotted state
Proxy automatically calls POST /vms/vm-abc123/restore
VM transitions to running (2-10 seconds)
Request is forwarded to the VM
The first request after a snapshot will have higher latency (restore time). Subsequent requests are instant. The proxy serializes concurrent restore requests to avoid redundant operations.
Disabled
curl -X POST https://api.hatch.example.com/vms/vm-abc123/routes \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"subdomain": "critical-app",
"target_port": 8080,
"auto_wake": false
}'
Behavior:
Request arrives for critical-app.hatch.example.com
VM is in snapshotted state
Proxy returns 503 Service Unavailable immediately
VM remains snapshotted
Response to client:
{
"error" : "vm is snapshotted and auto-wake is disabled"
}
Disable auto-wake for production VMs that must always be running. Use idle management carefully to avoid unwanted snapshots.
Multiple Routes Per VM
A single VM can have multiple proxy routes for different services:
# Route 1: Web interface
curl -X POST https://api.hatch.example.com/vms/vm-abc123/routes \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"subdomain": "web",
"target_port": 80
}'
# Route 2: API backend
curl -X POST https://api.hatch.example.com/vms/vm-abc123/routes \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"subdomain": "api",
"target_port": 8080
}'
# Route 3: Metrics endpoint
curl -X POST https://api.hatch.example.com/vms/vm-abc123/routes \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"subdomain": "metrics",
"target_port": 9090
}'
All routes share the same VM lifecycle. If the VM is snapshotted, any route with auto_wake: true can trigger a restore.
Listing Routes
All routes for a VM
curl https://api.hatch.example.com/vms/vm-abc123/routes \
-H "Authorization: Bearer YOUR_API_KEY"
Response:
[
{
"id" : "rt-xyz789" ,
"vm_id" : "vm-abc123" ,
"subdomain" : "my-app" ,
"target_port" : 8080 ,
"auto_wake" : true ,
"created_at" : "2026-03-06T14:30:00Z"
},
{
"id" : "rt-abc456" ,
"vm_id" : "vm-abc123" ,
"subdomain" : "my-api" ,
"target_port" : 3000 ,
"auto_wake" : false ,
"created_at" : "2026-03-06T10:15:00Z"
}
]
Deleting Routes
Remove a proxy route by its ID:
curl -X DELETE https://api.hatch.example.com/routes/rt-xyz789 \
-H "Authorization: Bearer YOUR_API_KEY"
Response:
Deleting a route does not affect the VM. The VM continues running, but the subdomain will no longer route to it.
Custom Domain Setup
To use your own domain (e.g., *.example.com) instead of the Hatch base domain:
Configure DNS wildcard record
Add a wildcard A or CNAME record pointing to your Hatch host: *.hatch.example.com A 203.0.113.10
Or using CNAME: *.hatch.example.com CNAME hatch-host.example.com
Configure Hatch base domain
Set the HATCH_BASE_DOMAIN environment variable: export HATCH_BASE_DOMAIN = hatch . example . com
Restart Hatch for the change to take effect.
Create routes as normal
Routes will now use your custom domain: curl -X POST https://api.hatch.example.com/vms/vm-abc123/routes \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"subdomain": "my-app",
"target_port": 8080
}'
Access at: https://my-app.hatch.example.com
You’ll also need a reverse proxy (nginx, Caddy, Traefik) in front of Hatch to handle TLS termination. The Hatch proxy speaks HTTP only.
Proxy Behavior
The original Host header is preserved when forwarding requests:
Client request: Host: my-app.hatch.example.com
Proxied request: Host: my-app.hatch.example.com
This allows applications to generate correct URLs and handle virtual hosting.
Request Timeout
Requests wait up to 30 seconds for auto-wake to complete (configurable via HATCH_PROXY_WAKE_TIMEOUT).
If restore takes longer:
{
"error" : "failed to wake vm"
}
Error Responses
Error Status Cause no subdomain in host header502 Host header missing or malformed no route for subdomain "x"502 No route exists for subdomain vm not found502 Route exists but VM was deleted vm is snapshotted and auto-wake is disabled503 VM snapshotted, auto-wake off failed to wake vm503 Restore operation failed vm is in state "error", not proxying503 VM in error state vm has no guest IP502 VM has networking disabled
Idle Tracking
The proxy tracks the last request time for each subdomain. This data is used by the idle monitor to determine when to snapshot inactive VMs.
Access tracking:
Every successful proxy request updates the last_access timestamp for the subdomain
VMs with proxy routes are considered active if they’ve received requests within the idle timeout
VMs without recent proxy traffic are candidates for automatic snapshotting
SSH connections are tracked separately via netfilter conntrack, not via proxy access times.
Best Practices
Use descriptive subdomain names
Choose subdomains that reflect the service or environment: prod-api, staging-web, dev-dashboard, qa-frontend
Enable auto-wake for dev/staging
Save costs by letting idle dev VMs snapshot automatically: {
"subdomain" : "dev-app" ,
"target_port" : 8080 ,
"auto_wake" : true
}
Disable auto-wake for production
Keep production VMs always running: {
"subdomain" : "prod-api" ,
"target_port" : 8080 ,
"auto_wake" : false
}
Use multiple routes for complex apps
Route different ports for frontend, backend, and admin panels: # Frontend
POST /vms/vm-abc123/routes {"subdomain": "app", "target_port": 80 }
# API
POST /vms/vm-abc123/routes {"subdomain": "api", "target_port": 8080 }
# Admin
POST /vms/vm-abc123/routes {"subdomain": "admin", "target_port": 9000 }
Next Steps
Idle Management Configure automatic snapshots based on proxy traffic
Snapshots Understand how auto-wake restores VMs
Creating VMs Learn about VM networking requirements
Network Setup Configure VMs for HTTP traffic