Nginx with TLS serving snapshots
When to use this: you want to run a production Generator behind Nginx with TLS, serving snapshot files to Host clients for fast historical bootstraping.
These scenarios use Ethereum Mainnet and Geth. Ethereum is the only officially supported chain today, but any EVM-compatible chain should work by changing chain.name and pointing the RPC URLs at a compatible node. See the chain config for details.
Topology
Nginx terminates TLS on port 443 and proxies health, metrics, and snapshot requests to the Generator on port 8080. Hosts connect to the Generator over P2P on port 9171 for live block data. Hosts can also pull snapshot files over HTTPS through Nginx for historical bootstrap.
Prerequisites
- Docker installed on the VM.
- TLS certificate and key files at
~/ssl/nginx.crtand~/ssl/nginx.key. You can use Let's Encrypt or your own CA. - A Geth node endpoint (self-hosted or managed provider).
- Ports 443 and 9171 open externally.
Compose file
This compose file is drawn from docker-compose-prod.yml in the shinzo-generator-client repo. Two things are changed for this scenario: SNAPSHOT_ENABLED is flipped to true so the Generator produces snapshot files Hosts can download through Nginx, and the Nginx port mapping is 443:443 (instead of the repo's 8080:8080) so Nginx terminates TLS on 443 as described below. Replace <YOUR_API_KEY_HERE> with your Geth API key:
networks:
shinzo-net:
driver: bridge
services:
shinzo-generator:
container_name: shinzo-generator
platform: linux/amd64
image: ghcr.io/shinzonetwork/shinzo-generator-client:standard
user: "1001:1001"
restart: unless-stopped
networks:
- shinzo-net
mem_limit: 16g
mem_reservation: 13g
ports:
- "9171:9171"
volumes:
- ~/shinzo-data/defradb:/app/.defra
environment:
- GETH_RPC_URL=https://json-rpc.aouzyll7wj7e9xe4g7t82w88c.blockchainnodeengine.com
- GETH_WS_URL=wss://ws.aouzyll7wj7e9xe4g7t82w88c.blockchainnodeengine.com
- GETH_API_KEY=<YOUR_API_KEY_HERE>
- GETH_API_KEY_TYPE=x-goog-api-key
- INDEXER_START_HEIGHT=0
- DEFRADB_KEYRING_SECRET=pingpong
- GOMEMLIMIT=14GiB
- SNAPSHOT_ENABLED=true
- SCHEMA_AUTH_MODE=none
logging:
options:
max-size: "50m"
max-file: "3"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
nginx:
image: nginx:alpine
ports:
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ~/ssl/nginx.crt:/etc/nginx/ssl/nginx.crt:ro
- ~/ssl/nginx.key:/etc/nginx/ssl/nginx.key:ro
depends_on:
- shinzo-generator
networks:
- shinzo-net
restart: unless-stopped
Nginx configuration
This Nginx config is drawn from nginx.conf (and the copy generated by indexer-prod-setup.sh) in the shinzo-generator-client repo. It proxies health, registration, registration-app, metrics, snapshot, and schema endpoints, and returns 404 for unmatched routes. The CORS headers allow requests from shinzo.network origins.
The repo's nginx.conf listens on 8080 as plaintext HTTP (the repo compose maps 8080:8080) and does not terminate TLS — the mounted certificate files are not referenced. To make this scenario actually terminate TLS as the title implies, the server block below changes listen 8080; to listen 443 ssl; and adds the ssl_certificate / ssl_certificate_key directives pointing at the cert and key files mounted from ~/ssl/. Everything else matches the repo file verbatim:
events { worker_connections 1024; }
http {
map $http_origin $cors_origin {
default "";
"~^https://[^/]+\.shinzo\.network$" $http_origin;
}
server {
listen 443 ssl;
server_name _;
ssl_certificate /etc/nginx/ssl/nginx.crt;
ssl_certificate_key /etc/nginx/ssl/nginx.key;
add_header 'Access-Control-Allow-Origin' $cors_origin always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, Accept, Origin' always;
add_header 'Access-Control-Max-Age' 3600 always;
add_header 'Vary' 'Origin' always;
location = /health {
if ($request_method = OPTIONS) { return 204; }
proxy_pass http://shinzo-generator:8080/health;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
location = /registration {
if ($request_method = OPTIONS) { return 204; }
proxy_pass http://shinzo-generator:8080/registration;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
location = /registration-app {
if ($request_method = OPTIONS) { return 204; }
proxy_pass http://shinzo-generator:8080/registration-app;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
location = /metrics {
if ($request_method = OPTIONS) { return 204; }
proxy_pass http://shinzo-generator:8080/metrics;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
location = /snapshots {
if ($request_method = OPTIONS) { return 204; }
proxy_pass http://shinzo-generator:8080/snapshots;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
location ~ ^/snapshots/(.+)$ {
if ($request_method = OPTIONS) { return 204; }
proxy_pass http://shinzo-generator:8080/snapshots/$1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_buffering off;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
client_max_body_size 0;
}
# Schema endpoint
location = /api/v1/schema {
if ($request_method = OPTIONS) { return 204; }
proxy_pass http://shinzo-generator:8080/api/v1/schema;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
location ~ ^/api/v1/schema/(.+)$ {
if ($request_method = OPTIONS) { return 204; }
proxy_pass http://shinzo-generator:8080/api/v1/schema/$1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Default 404 for unmatched routes
location / {
return 404;
}
}
}
What the key values mean
SNAPSHOT_ENABLED=true: The Generator produces signed snapshot files before pruning. The originaldocker-compose-prod.ymlsets this tofalse. This scenario enables it so Hosts can bootstrap from snapshots. See snapshot config.SCHEMA_AUTH_MODE=none: Disables authentication on the/api/v1/schemaendpoints that Nginx proxies. The code default istoken, which requires keys viaSCHEMA_API_KEYSor every schema request returns 503. The repo'sdocker-compose-prod.ymlsetsnone. See indexer config.GETH_API_KEY_TYPE=x-goog-api-key: Header name for GCP BNE authentication. See geth config.GOMEMLIMIT=14GiB: Go runtime soft memory limit, set below the 16g container limit. See env vars.- The Nginx
proxy_buffering offand extended timeouts on/snapshots/(.+)$prevent Nginx from buffering large snapshot downloads.client_max_body_size 0removes the request body size limit.
Start the Generator
Place both files in the same directory and start:
docker compose -f docker-compose.yml up -d
Verify the Generator is healthy through Nginx:
curl https://your-domain/health
List available snapshots:
curl https://your-domain/snapshots
Registration
Once the Generator is running, register it with the Shinzo Network. See Registration.
Gotchas
- The original
docker-compose-prod.ymlsetsSNAPSHOT_ENABLED=false. This scenario changes it totruebecause the purpose is to serve snapshots to Hosts. If you keep itfalse, no snapshot files are produced and the/snapshotsendpoint returns nothing. - The Nginx config terminates TLS on port 443 (
listen 443 ssl;withssl_certificate/ssl_certificate_keypointing at/etc/nginx/ssl/nginx.crtand/etc/nginx/ssl/nginx.key), and the compose maps host443to container443. You must place your certificate and key at~/ssl/nginx.crtand~/ssl/nginx.keyor Nginx will fail to start. The repo'snginx.conflistens on8080as plaintext and maps8080:8080— it does not terminate TLS — so thelisten … sslandssl_certificate*directives above are the only additions this scenario makes beyond the repo file. - The image tag
ghcr.io/shinzonetwork/shinzo-generator-client:standardin this compose matchesdocker-compose-prod.yml. The repo'sindexer-prod-setup.shpins a versioned tag (ghcr.io/shinzonetwork/shinzo-generator-client:v0.6.5.1-ethereum-mainnet), and the install page usesghcr.io/shinzonetwork/shinzo-generator-client:ethereum-mainnet-latest. Docker pull/run tag strategy is being consolidated in issues #326 and #327; align with whatever those land on rather than mixing tags across deployments. LOG_LEVEL,LOG_SOURCE, andLOG_STACKTRACEappear in the originaldocker-compose-prod.ymlbut are not read by the Generator client, so they are omitted here.SCHEMA_AUTH_MODE=noneis kept because the Generator client does read it (it controls auth on the/api/v1/schemaendpoints Nginx proxies). See the env vars table for details.- The snapshot directory defaults to
./snapshotsinside the container. Snapshots are written to the DefraDB data directory at~/shinzo-data/defradb/snapshotson the host because of the volume mount. Hosts download them through Nginx, not directly from the filesystem. - The
proxy_read_timeoutandproxy_send_timeoutare set to 300 seconds for snapshot downloads. Large snapshot files can take time to transfer. If Hosts time out downloading, increase these values.
Need help
- For onboarding and technical support, join the Shinzo Discord.
- To report a documentation bug or request a feature, open an issue in the docs repo.
- For a technical issue with the Generator client, open an issue in the shinzo-generator-client repo.