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

flowchart LR subgraph VM["Prod VM"] direction LR Nginx["<b>Nginx</b><br/>:443 TLS"] Gen["<b>Generator client</b><br/>:8080 Health/Metrics/Snapshots<br/>:9171 P2P"] Nginx -- "proxy :8080" --> Gen end Hosts["Hosts"] Gen -- "P2P (libp2p)<br/>:9171" --> Hosts Hosts -- "HTTPS<br/>/snapshots" --> Nginx

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.crt and ~/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 original docker-compose-prod.yml sets this to false. This scenario enables it so Hosts can bootstrap from snapshots. See snapshot config.
  • SCHEMA_AUTH_MODE=none: Disables authentication on the /api/v1/schema endpoints that Nginx proxies. The code default is token, which requires keys via SCHEMA_API_KEYS or every schema request returns 503. The repo's docker-compose-prod.yml sets none. 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 off and extended timeouts on /snapshots/(.+)$ prevent Nginx from buffering large snapshot downloads. client_max_body_size 0 removes 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.yml sets SNAPSHOT_ENABLED=false. This scenario changes it to true because the purpose is to serve snapshots to Hosts. If you keep it false, no snapshot files are produced and the /snapshots endpoint returns nothing.
  • The Nginx config terminates TLS on port 443 (listen 443 ssl; with ssl_certificate / ssl_certificate_key pointing at /etc/nginx/ssl/nginx.crt and /etc/nginx/ssl/nginx.key), and the compose maps host 443 to container 443. You must place your certificate and key at ~/ssl/nginx.crt and ~/ssl/nginx.key or Nginx will fail to start. The repo's nginx.conf listens on 8080 as plaintext and maps 8080:8080 — it does not terminate TLS — so the listen … ssl and ssl_certificate* directives above are the only additions this scenario makes beyond the repo file.
  • The image tag ghcr.io/shinzonetwork/shinzo-generator-client:standard in this compose matches docker-compose-prod.yml. The repo's indexer-prod-setup.sh pins a versioned tag (ghcr.io/shinzonetwork/shinzo-generator-client:v0.6.5.1-ethereum-mainnet), and the install page uses ghcr.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, and LOG_STACKTRACE appear in the original docker-compose-prod.yml but are not read by the Generator client, so they are omitted here. SCHEMA_AUTH_MODE=none is kept because the Generator client does read it (it controls auth on the /api/v1/schema endpoints Nginx proxies). See the env vars table for details.
  • The snapshot directory defaults to ./snapshots inside the container. Snapshots are written to the DefraDB data directory at ~/shinzo-data/defradb/snapshots on the host because of the volume mount. Hosts download them through Nginx, not directly from the filesystem.
  • The proxy_read_timeout and proxy_send_timeout are 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