Hydra
Docs

Expose your Hydra Node securely with Nginx, Caddy, or Traefik.

Reverse Proxy Setup

Running your Hydra Node behind a reverse proxy is strongly recommended for production deployments. A reverse proxy provides:

  • TLS/SSL termination — serve your node over HTTPS with automatic certificates
  • DDoS protection — rate limiting and connection throttling at the proxy level
  • WebSocket support — proper upgrade header forwarding
  • Domain routing — serve your node on a clean domain like node.example.com
  • Security — hide internal ports and services from the internet

Architecture

Internet


┌──────────────────┐
│  Reverse Proxy   │  :443 (HTTPS) / :80 (HTTP → redirect)
│  (Nginx/Caddy)   │
└────────┬─────────┘
         │ proxy_pass

┌──────────────────┐
│   Hydra Node     │  :4001 (localhost only)
│   REST + WS      │
└──────────────────┘

When using a reverse proxy, change your node config to bind only to localhost:

server:
  port: 4001
  host: "127.0.0.1"  # Only accept connections from the proxy
  domain: "node.example.com"

Your firewall should only allow external access on ports 80 and 443 (the proxy ports). Port 4001 should not be accessible from the internet.


Nginx

Nginx is the most popular reverse proxy. The configuration below handles both regular HTTP requests and WebSocket upgrades.

Install Nginx

# Ubuntu / Debian
sudo apt update && sudo apt install -y nginx

# CentOS / RHEL
sudo yum install -y nginx

# macOS
brew install nginx

Configuration

Create /etc/nginx/sites-available/hydra-node:

# Upgrade map for WebSocket support
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 80;
    server_name node.example.com;

    # Redirect HTTP → HTTPS
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name node.example.com;

    # ── TLS Certificates ──────────────────────────────────
    # Replace with your certificate paths
    # (or use Certbot for automatic Let's Encrypt certs)
    ssl_certificate     /etc/letsencrypt/live/node.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/node.example.com/privkey.pem;

    # ── TLS Settings ──────────────────────────────────────
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;

    # ── Proxy to Hydra Node ───────────────────────────────
    location / {
        proxy_pass http://127.0.0.1:4001;

        # Standard proxy headers
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # WebSocket support
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

        # Timeouts — WebSocket connections can be long-lived
        proxy_read_timeout 86400s;
        proxy_send_timeout 86400s;
    }

    # ── Optional: Rate Limiting ──────────────────────────
    # Uncomment to add proxy-level rate limiting
    # limit_req_zone $binary_remote_addr zone=hydra:10m rate=30r/s;
    # limit_req zone=hydra burst=50 nodelay;
}

Enable and Start

# Enable the site
sudo ln -s /etc/nginx/sites-available/hydra-node /etc/nginx/sites-enabled/

# Test the configuration
sudo nginx -t

# Reload Nginx
sudo systemctl reload nginx

Automatic SSL with Certbot

# Install Certbot
sudo apt install -y certbot python3-certbot-nginx

# Obtain and install certificate
sudo certbot --nginx -d node.example.com

# Certbot automatically modifies your Nginx config and sets up auto-renewal
# Verify auto-renewal
sudo certbot renew --dry-run

After Certbot runs, your node is accessible at https://node.example.com with automatic certificate renewal.


Caddy

Caddy is the easiest option — it handles TLS certificates automatically with zero configuration. No Certbot needed.

Install Caddy

# Ubuntu / Debian
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install -y caddy

# macOS
brew install caddy

# Docker
docker pull caddy:latest

Configuration

Create /etc/caddy/Caddyfile:

node.example.com {
    # Automatic HTTPS — Caddy obtains and renews Let's Encrypt certs

    # Proxy all traffic to Hydra Node
    reverse_proxy 127.0.0.1:4001

    # Logging
    log {
        output file /var/log/caddy/hydra-node.log
        format json
    }

    # Optional: rate limiting (requires caddy-ratelimit plugin)
    # rate_limit {
    #     zone dynamic {
    #         key    {remote_host}
    #         events 60
    #         window 1m
    #     }
    # }
}

That's it. Caddy automatically:

  • Obtains a Let's Encrypt TLS certificate for node.example.com
  • Redirects HTTP to HTTPS
  • Handles WebSocket upgrades
  • Renews certificates before they expire

Start Caddy

# Reload with new config
sudo systemctl reload caddy

# Or start fresh
sudo systemctl enable caddy
sudo systemctl start caddy

# Check status
sudo systemctl status caddy

Caddy with Docker Compose

version: "3.8"

services:
  caddy:
    image: caddy:latest
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    restart: unless-stopped

  hydra-node:
    image: ghcr.io/hydra-network/hydra-node:latest
    volumes:
      - ./node.yaml:/etc/hydra/node.yaml:ro
      - hydra-data:/var/hydra/data
    env_file:
      - .env
    command: start --config /etc/hydra/node.yaml
    restart: unless-stopped

volumes:
  caddy_data:
  caddy_config:
  hydra-data:

With the Caddyfile:

node.example.com {
    reverse_proxy hydra-node:4001
}

When using Docker Compose, the proxy references the service name hydra-node instead of 127.0.0.1.


Traefik

Traefik is ideal for Docker-native setups and Kubernetes. It auto-discovers services via Docker labels.

Docker Compose with Traefik

version: "3.8"

services:
  traefik:
    image: traefik:v3.0
    command:
      - "--api.dashboard=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedByDefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
      - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
      - "--certificatesresolvers.letsencrypt.acme.email=admin@example.com"
      - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
      # HTTP → HTTPS redirect
      - "--entrypoints.web.http.redirections.entryPoint.to=websecure"
      - "--entrypoints.web.http.redirections.entryPoint.scheme=https"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - traefik-certs:/letsencrypt
    restart: unless-stopped

  hydra-node:
    image: ghcr.io/hydra-network/hydra-node:latest
    labels:
      - "traefik.enable=true"
      # Router
      - "traefik.http.routers.hydra-node.rule=Host(`node.example.com`)"
      - "traefik.http.routers.hydra-node.entrypoints=websecure"
      - "traefik.http.routers.hydra-node.tls.certresolver=letsencrypt"
      # Service
      - "traefik.http.services.hydra-node.loadbalancer.server.port=4001"
    volumes:
      - ./node.yaml:/etc/hydra/node.yaml:ro
      - hydra-data:/var/hydra/data
    env_file:
      - .env
    command: start --config /etc/hydra/node.yaml
    restart: unless-stopped

volumes:
  traefik-certs:
  hydra-data:

Traefik automatically:

  • Discovers the hydra-node service via Docker labels
  • Obtains Let's Encrypt certificates
  • Routes node.example.com to the node on port 4001
  • Handles WebSocket upgrades natively

Start

docker compose up -d

# View Traefik dashboard (if enabled)
# http://localhost:8080/dashboard/

Cloudflare Tunnel (No Open Ports)

If you don't want to open any ports on your server, you can use Cloudflare Tunnel to expose your node via Cloudflare's network.

Install cloudflared

# Linux
curl -L -o cloudflared https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64
chmod +x cloudflared
sudo mv cloudflared /usr/local/bin/

# macOS
brew install cloudflare/cloudflare/cloudflared

Create and Configure Tunnel

# Login to Cloudflare
cloudflared tunnel login

# Create a tunnel
cloudflared tunnel create hydra-node

# Configure the tunnel
cat > ~/.cloudflared/config.yml << 'EOF'
tunnel: <TUNNEL_ID>
credentials-file: /home/user/.cloudflared/<TUNNEL_ID>.json

ingress:
  - hostname: node.example.com
    service: http://127.0.0.1:4001
    originRequest:
      # Required for WebSocket support
      noTLSVerify: false
  - service: http_status:404
EOF

# Create a DNS record pointing to the tunnel
cloudflared tunnel route dns hydra-node node.example.com

# Run the tunnel
cloudflared tunnel run hydra-node

Run as a Service

sudo cloudflared service install
sudo systemctl enable cloudflared
sudo systemctl start cloudflared

Note: Cloudflare Tunnel adds latency compared to direct connections. For time-sensitive WebSocket streams, a direct Nginx/Caddy proxy is preferred.


Firewall Configuration

Regardless of which proxy you use, configure your firewall to only expose the necessary ports:

UFW (Ubuntu)

# Allow SSH
sudo ufw allow 22/tcp

# Allow HTTP and HTTPS (for the reverse proxy)
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

# Do NOT allow 4001 directly — traffic goes through the proxy
# sudo ufw allow 4001/tcp  ← DON'T DO THIS

# Enable firewall
sudo ufw enable
sudo ufw status

iptables

# Allow established connections
sudo iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# Allow SSH
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT

# Allow HTTP/HTTPS
sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT

# Allow localhost (for control plane on 4002)
sudo iptables -A INPUT -i lo -j ACCEPT

# Drop everything else
sudo iptables -A INPUT -j DROP

Verifying Your Setup

After configuring the reverse proxy, verify everything works:

# 1. Health check over HTTPS
curl https://node.example.com/health

# 2. Status endpoint
curl https://node.example.com/status

# 3. Fetch signals
curl https://node.example.com/public/signals

# 4. WebSocket connection
wscat -c wss://node.example.com/stream/live

# 5. Test from the platform dashboard
# Go to Nodes → your node → click "Test Connection"

If the platform shows your node as Online and the test connection succeeds, your reverse proxy is correctly configured.


Common Issues

WebSocket connections drop immediately

Your proxy isn't forwarding the Upgrade and Connection headers. Ensure your Nginx config includes:

proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;

504 Gateway Timeout on WebSocket

Increase the proxy timeout. WebSocket connections are long-lived:

proxy_read_timeout 86400s;  # 24 hours
proxy_send_timeout 86400s;

SSL certificate errors

  • Ensure your domain's DNS A record points to your server's IP
  • Wait a few minutes after DNS changes for propagation
  • For Certbot: verify port 80 is open for the HTTP challenge
  • For Caddy: it handles everything automatically — just ensure ports 80 and 443 are open

Node shows "Offline" on platform but works locally

The platform tests connectivity to your registered endpoint. Make sure:

  1. Your endpoint URL on the platform matches your proxy domain (e.g. https://node.example.com, not http://localhost:4001)
  2. The proxy is running and DNS resolves correctly
  3. No firewall is blocking inbound HTTPS traffic