A Python FastAPI gateway that gives every Aleph VM a deterministic *.2n6.me URL via L4 SNI passthrough on HAProxy.
- Sync — Fetches all INSTANCE messages from the Aleph network API
- Derive — Converts each instance hash to a 4-word BIP-39 subdomain (e.g.,
swift-falcon-arrow-piano.2n6.me) - Resolve — Bulk-scans all CRNs (Compute Resource Nodes) to find running VMs and their IPv6 addresses
- Route — Generates HAProxy map files for SNI-based routing
User Request HAProxy Aleph VM
| | |
| swift-falcon-arrow-piano.2n6.me |
|--------------------------->| |
| | SNI lookup → IPv6 |
| |------------------------------->|
| |<-------------------------------|
|<---------------------------| |
Subdomains are derived deterministically from instance hashes using the BIP-39 wordlist (2048 words). The top 44 bits of the hash select 4 words (11 bits each):
Instance Hash: a3f8b2c1d4e5f6...
↓
Top 44 bits → 4 × 11-bit indices
↓
Subdomain: physical-sheriff-race-soul
Same hash always produces the same subdomain. No database lookup needed.
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt# Development
python run.py
# Production
uvicorn gateway.main:app --host 0.0.0.0 --port 8000Environment variables or create_app() parameters:
| Env var | create_app() param |
Default | Description |
|---|---|---|---|
| — | domain |
2n6.me |
Base domain for subdomains |
| — | cache_path |
/var/lib/gateway/cache.json |
Persistent cache location |
| — | haproxy_config_dir |
/etc/haproxy |
HAProxy config directory |
HAPROXY_RELOAD_CMD |
haproxy_reload_command |
systemctl reload haproxy |
Command to reload HAProxy |
| — | sync_interval |
300 |
Seconds between syncs |
CERT_PATH |
cert_path |
None | Path for combined PEM (e.g., /etc/haproxy/certs/api.2n6.me.pem). TLS disabled if unset. |
CERT_EMAIL |
cert_email |
None | Email for Let's Encrypt. Required for auto-provisioning. |
ACME_WEBROOT |
acme_webroot |
/var/lib/gateway/acme |
Directory for ACME HTTP-01 challenge files |
Look up an instance by its subdomain.
{
"subdomain": "swift-falcon-arrow-piano",
"url": "swift-falcon-arrow-piano.2n6.me",
"instance_hash": "a3f8b2c1d4e5f6...",
"ipv6": "2001:db8::1",
"active": true
}Look up an instance by its hash.
{
"instance_hash": "a3f8b2c1d4e5f6...",
"subdomain": "physical-sheriff-race-soul",
"url": "physical-sheriff-race-soul.2n6.me",
"ipv6": "2001:db8::1",
"active": true
}Gateway status.
{
"instance_count": 425,
"route_count": 184,
"last_sync_time": 1700000000.0,
"domain": "2n6.me"
}Trigger an immediate sync.
gateway/
├── main.py # FastAPI app, lifespan, API endpoints, ACME challenge
├── aleph_sync.py # Fetches INSTANCE messages from Aleph API
├── subdomain.py # BIP-39 hash-to-subdomain conversion
├── resolver.py # Bulk CRN scanning for IPv6 resolution
├── haproxy.py # HAProxy config generation (conditional TLS)
├── tls.py # Let's Encrypt cert automation
└── cache.py # Persistent JSON cache
Aleph API Gateway CRNs
| | |
| GET /messages.json | |
|<--------------------------| |
| INSTANCE messages | |
|-------------------------->| |
| | |
| | GET /about/executions/list |
| |----------------------------->|
| | {hash: {ipv6: ...}} |
| |<-----------------------------|
| | |
| | Write HAProxy maps |
| |---> sni.map, backends.cfg |
The gateway generates the full haproxy.cfg on each sync cycle (base config + dynamic backends). It also writes:
sni.map— Maps FQDN to backend name for TLS SNI routinghost.map— Maps FQDN to backend name for HTTP Host header routing
The management API is exposed publicly via api.2n6.me through an HAProxy ACL (no extra DNS record needed since the wildcard covers it).
See haproxy/haproxy.cfg.template for the base config reference.
The API (api.2n6.me) supports automatic HTTPS via Let's Encrypt. HAProxy's :443 frontend is TCP mode for VM SNI passthrough, so the API uses the loopback pattern to selectively terminate TLS:
:443 ft_ssl (TCP) ──┬── SNI api.2n6.me ──→ bk_api_loopback (127.0.0.1:8443)
│ ↓
│ ft_api_tls (TLS terminated, HTTP)
│ ↓
│ bk_gateway_api (127.0.0.1:8000 → FastAPI)
│
└── SNI *.2n6.me ──→ VM backends (passthrough, unchanged)
:80 ft_http (HTTP) ──┬── /.well-known/acme-challenge/* ──→ bk_gateway_api
├── api.2n6.me (other) ──→ 301 redirect to HTTPS
└── *.2n6.me ──→ VM HTTP backends (unchanged)
Lifecycle:
- First start (no cert): HAProxy config is HTTP-only, ACME endpoint active. A background task runs certbot, obtains the cert, builds the combined PEM, installs a renewal hook, and regenerates HAProxy config with TLS. The API becomes HTTPS within ~30s of first boot.
- Subsequent starts (cert exists): HAProxy config immediately includes TLS sections.
- Renewal: Certbot's systemd timer runs
certbot renewtwice daily. The deploy hook at/etc/letsencrypt/renewal-hooks/deploy/gateway.shrebuilds the combined PEM and reloads HAProxy automatically.
- A server with a public IPv4 and IPv6 address
- A wildcard DNS record (
*.yourdomain) pointing to the server - Python 3.12+
- HAProxy 2.8+
These steps were used to deploy to the current production server (46.247.131.199 / 2a05:6e02:1067:f510:d021:8dff:fe1a:a478).
sudo apt-get update && sudo apt-get install -y haproxy python3-venv certbotsudo mkdir -p /opt/aleph-domain-gateway
sudo chown ubuntu:ubuntu /opt/aleph-domain-gateway
# From development machine:
rsync -avz --exclude='.venv' --exclude='__pycache__' --exclude='.pytest_cache' --exclude='.git' \
./ ubuntu@<server>:/opt/aleph-domain-gateway/cd /opt/aleph-domain-gateway
python3 -m venv .venv
.venv/bin/pip install -r requirements.txtsudo mkdir -p /var/lib/gateway
sudo mkdir -p /var/lib/gateway/acme/.well-known/acme-challenge
sudo mkdir -p /etc/haproxy/certs
sudo chown -R ubuntu:ubuntu /var/lib/gateway /etc/haproxy/certsThe gateway writes haproxy.cfg directly, so it needs write access:
sudo chown ubuntu:ubuntu /etc/haproxy/haproxy.cfg
sudo touch /etc/haproxy/sni.map /etc/haproxy/host.map
sudo chown ubuntu:ubuntu /etc/haproxy/sni.map /etc/haproxy/host.mapAllow the gateway user to reload HAProxy and run certbot without a password:
echo 'ubuntu ALL=(ALL) NOPASSWD: /usr/bin/systemctl reload haproxy' | sudo tee /etc/sudoers.d/gateway-haproxy
sudo chmod 440 /etc/sudoers.d/gateway-haproxy
echo 'ubuntu ALL=(ALL) NOPASSWD: /usr/bin/certbot, /usr/bin/sh -c cat /etc/letsencrypt/*, /usr/bin/tee /etc/letsencrypt/*, /usr/bin/chmod * /etc/letsencrypt/*' | sudo tee /etc/sudoers.d/gateway-certbot
sudo chmod 440 /etc/sudoers.d/gateway-certbotcat <<'EOF' | sudo tee /etc/systemd/system/aleph-gateway.service
[Unit]
Description=Aleph Domain Gateway
After=network.target haproxy.service
[Service]
Type=simple
User=ubuntu
WorkingDirectory=/opt/aleph-domain-gateway
Environment=CERT_PATH=/etc/haproxy/certs/api.2n6.me.pem
Environment=CERT_EMAIL=letsencrypt@aleph.cloud
Environment=ACME_WEBROOT=/var/lib/gateway/acme
Environment="HAPROXY_RELOAD_CMD=sudo systemctl reload haproxy"
ExecStart=/opt/aleph-domain-gateway/.venv/bin/uvicorn gateway.main:app --host 127.0.0.1 --port 8000
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now aleph-gatewayTo disable automatic TLS, omit the CERT_PATH and CERT_EMAIL lines.
Add a wildcard record for your domain pointing to the server. For Gandi:
| Record | Type | Value |
|---|---|---|
* |
A | 46.247.131.199 |
* |
AAAA | 2a05:6e02:1067:f510:d021:8dff:fe1a:a478 |
# Check gateway is running
curl https://api.2n6.me/api/status
# Look up a specific instance
curl https://api.2n6.me/api/hash/<instance_hash>
# Check HTTP redirects to HTTPS
curl -sI http://api.2n6.me/api/status
# → 301 Moved Permanently, Location: https://...
# Check TLS certificate
echo | openssl s_client -connect api.2n6.me:443 -servername api.2n6.me 2>/dev/null | openssl x509 -noout -subject -dates
# Check HAProxy is routing VMs
curl -sk https://<subdomain>.2n6.me/# From development machine:
rsync -avz --exclude='.venv' --exclude='__pycache__' --exclude='.pytest_cache' --exclude='.git' \
./ ubuntu@<server>:/opt/aleph-domain-gateway/
# On server:
sudo systemctl restart aleph-gatewayThe gateway takes ~30-40 seconds to start (syncs instances + scans all CRNs).
pytest tests/ -v| API | Purpose |
|---|---|
api2.aleph.im/api/v0/messages.json |
Fetch INSTANCE messages |
crns-list.aleph.sh/crns.json |
Bulk list of all CRN URLs |
{crn}/v2/about/executions/list |
Running VMs on a CRN (with actual IPv6) |
MIT