Sign inSign up

janmbaco/go-reverseproxy-ssl

By janmbaco

•Updated 11 months ago

Go HTTPS reverse proxy: auto Let’s Encrypt, optional mTLS, HTTP/2, REST & gRPC-Web.

Image
Security
API management
Web servers
0

10K+

janmbaco/go-reverseproxy-ssl repository overview

⁠Go ReverseProxy SSL

Go Report Card Go Version License Release

Add automatic HTTPS to any web app, API, or gRPC service in 2 minutes. No Nginx config, no manual certificates — just point your domain and run.

⁠What It Does

You have a web app running on localhost:3000. You want:

  • ✓ Automatic HTTPS with Let's Encrypt certificates
  • ✓ No configuration hell (just a simple JSON file)
  • ✓ Multiple apps/APIs on different subdomains
  • ✓ gRPC and SSH support with the same certificates
  • ✓ Web UI to manage virtual hosts

This reverse proxy handles all of that automatically.

Your App (localhost:3000) + This Proxy = https://yourdomain.com (automatic cert)
⁠When to Use This

✅ Primary use case:

  • VPS/dedicated server (DigitalOcean, Linode, Hetzner, OVH)
  • Cloud VM (AWS EC2, Google Compute Engine, Azure Virtual Machines)
  • Self-hosted server at home or office
  • Docker Compose setup with multiple services
  • Full control over ports 80 and 443

✅ Advanced use case (centralized reverse proxy):

  • Unified entry point for multiple PaaS backends (Heroku, Vercel, Azure App Service, etc.)
  • Custom domain management with Let's Encrypt for all services
  • Route api.yourdomain.com → Heroku app, web.yourdomain.com → Vercel, etc.

⁠Quick Start

⁠Prerequisites
  1. Your own server (VPS or cloud VM with root access)
  2. Domain name pointed to your server's IP (e.g., example.com → 203.0.113.10)
  3. Ports 80 and 443 open in your firewall
  4. Docker (recommended) or Go 1.25+
⁠Example 1: Single Web Application

You have a Node.js API running on port 3000 in your DigitalOcean droplet. Want it accessible via https://myapp.com with automatic SSL:

Using Docker with environment variables:

docker run -d \
  --name reverseproxy \
  -p 80:80 -p 443:443 -p 8081:8081 \
  -e DOMAIN=myapp.com \
  -e BACKEND_HOST=localhost \
  -e BACKEND_PORT=3000 \
  -e BACKEND_SCHEME=http \
  -v reverseproxy-certs:/app/certs \
  --network host \
  janmbaco/go-reverseproxy-ssl:v3

That's it! Visit https://myapp.com — certificate is automatically issued by Let's Encrypt.

How it works:

  1. DNS myapp.com → Your VPS IP (203.0.113.10)
  2. Proxy listens on ports 80/443
  3. Let's Encrypt validates via HTTP-01 challenge (port 80)
  4. Certificate issued and cached in /app/certs
  5. Proxy forwards HTTPS requests to http://localhost:3000

Platform-specific notes:

  • DigitalOcean/Linode Droplet: Use --network host or backend service IP
  • AWS EC2 / Azure VM: Ensure ports 80/443 in security groups/NSG
  • Docker Compose: Replace localhost with service name (see Example 3)

⁠Example 2: Multiple Subdomains (ENV vars only)

You have three services and want separate subdomains with automatic HTTPS — no config.json needed:

  • React app on port 3000 → www.myapp.com
  • Express API on port 8080 → api.myapp.com
  • Admin dashboard on port 5000 → app.myapp.com

Using Docker with environment variables:

docker run -d \
  --name reverseproxy \
  -p 80:80 -p 443:443 -p 8081:8081 \
  -e WWW_DOMAIN=www.myapp.com \
  -e WWW_BACKEND=localhost:3000 \
  -e API_DOMAIN=api.myapp.com \
  -e API_BACKEND=localhost:8080 \
  -e APP_DOMAIN=app.myapp.com \
  -e APP_BACKEND=localhost:5000 \
  -v reverseproxy-certs:/app/certs \
  --network host \
  janmbaco/go-reverseproxy-ssl:v3

Result:

  • https://www.myapp.com → React app (automatic cert)
  • https://api.myapp.com → Express API (automatic cert)
  • https://app.myapp.com → Admin dashboard (automatic cert)

Backend format:

  • Basic: host:port (defaults to HTTP)
  • With scheme: host:port:https (for HTTPS backends)

Examples:

-e WWW_BACKEND=frontend-service:3000           # HTTP backend
-e API_BACKEND=api.herokuapp.com:443:https    # HTTPS backend
-e APP_BACKEND=172.17.0.1:5000                # Docker bridge IP

⁠Example 3: Multiple Services (Docker Compose)

Same setup as Example 2, but using Docker Compose with custom config.json for advanced features:

You have:

  • Frontend on port 3000 → www.myapp.com
  • API on port 8080 → api.myapp.com
  • Admin panel on port 5000 → admin.myapp.com

docker-compose.yml:

version: "3.9"

services:
  reverseproxy:
    image: janmbaco/go-reverseproxy-ssl:v3
    ports:
      - "80:80"
      - "443:443"
      - "8081:8081"
    volumes:
      - ./config.json:/app/config/config.json:ro
      - certs:/app/certs
    networks: [app]

  frontend:
    image: my-frontend:latest
    expose: ["3000"]
    networks: [app]

  api:
    image: my-api:latest
    expose: ["8080"]
    networks: [app]

  admin:
    image: my-admin:latest
    expose: ["5000"]
    networks: [app]

networks:
  app:
volumes:
  certs:

config.json:

{
  "web_virtual_hosts": [
    {
      "from": "www.myapp.com",
      "scheme": "http",
      "host_name": "frontend",
      "port": 3000
    },
    {
      "from": "api.myapp.com",
      "scheme": "http",
      "host_name": "api",
      "port": 8080
    },
    {
      "from": "admin.myapp.com",
      "scheme": "http",
      "host_name": "admin",
      "port": 5000
    }
  ],
  "default_host": "www.myapp.com",
  "reverse_proxy_port": ":443",
  "config_ui_port": ":8081"
}

Run: docker-compose up -d

Result:

  • https://www.myapp.com → frontend (automatic cert)
  • https://api.myapp.com → API (automatic cert)
  • https://admin.myapp.com → admin panel (automatic cert)

⁠Example 4: gRPC-Web Service

Backend gRPC service on port 9090, expose as https://grpc.myapp.com for browser access:

Simple config (transparent mode - proxies all services/methods):

{
  "grpc_web_virtual_hosts": [
    {
      "from": "grpc.myapp.com",
      "scheme": "http",
      "host_name": "grpc-service",
      "port": 9090,
      "grpc_web_proxy": {
        "is_transparent_server": true,
        "allow_all_origins": true
      }
    }
  ],
  "default_host": "grpc.myapp.com",
  "reverse_proxy_port": ":443"
}

Advanced config (selective service/method proxying):

{
  "grpc_web_virtual_hosts": [
    {
      "from": "grpc.myapp.com",
      "scheme": "http",
      "host_name": "grpc-service",
      "port": 9090,
      "grpc_web_proxy": {
        "grpc_services": {
          "hello.HelloService": ["SayHello", "ServerStreamingChat"],
          "user.UserService": ["GetUser", "UpdateUser"]
        },
        "is_transparent_server": false,
        "authority": "grpc-service:9090",
        "allow_all_origins": false,
        "allowed_origins": ["https://myapp.com"]
      }
    }
  ],
  "default_host": "grpc.myapp.com",
  "reverse_proxy_port": ":443"
}

JavaScript gRPC-Web client:

const client = new GreeterClient('https://grpc.myapp.com');

Note: The grpc_web_proxy field is required for gRPC-Web virtual hosts. Set is_transparent_server: true to proxy all services automatically, or set it to false and specify grpc_services for fine-grained control.


⁠Web Configuration UI

Instead of editing JSON, use the built-in web interface:

  1. Access UI: http://localhost:8081 (or your server IP:8081)
  2. Add virtual host: Click "New Virtual Host"
  3. Fill form:
    • Domain: example.com
    • Backend Host: my-app
    • Backend Port: 3000
    • Protocol: http
  4. Save → Configuration updated automatically

UI Features:

  • Visual virtual host management
  • Certificate upload for custom certs
  • Configuration preview (JSON)
  • Live validation

Security: Bind to 127.0.0.1:8081 for localhost-only access, or put behind authentication.


⁠Common Use Cases

⁠Use Case 1: "I have a React dev server on localhost:3000"

Minimal config:

{
  "web_virtual_hosts": [
    {
      "from": "myapp.com",
      "scheme": "http",
      "host_name": "localhost",
      "port": 3000
    }
  ],
  "default_host": "myapp.com",
  "reverse_proxy_port": ":443"
}

Docker command:

docker run -d \
  -p 80:80 -p 443:443 \
  -e DOMAIN=myapp.com \
  -e BACKEND_HOST=host.docker.internal \
  -e BACKEND_PORT=3000 \
  -v certs:/app/certs \
  janmbaco/go-reverseproxy-ssl:v3

Verify:

curl -I https://myapp.com  # Should return 200 with Let's Encrypt cert

⁠Use Case 2: "I have API + Frontend in Docker Compose"

Scenario: Express API (port 8080) + Next.js (port 3000)

docker-compose.yml:

services:
  proxy:
    image: janmbaco/go-reverseproxy-ssl:v3
    ports: ["80:80", "443:443"]
    volumes:
      - ./config.json:/app/config/config.json:ro
      - certs:/app/certs
    networks: [backend]

  api:
    build: ./api
    expose: ["8080"]
    networks: [backend]

  web:
    build: ./web
    expose: ["3000"]
    networks: [backend]

networks:
  backend:
volumes:
  certs:

config.json:

{
  "web_virtual_hosts": [
    {"from": "api.example.com", "host_name": "api", "port": 8080, "scheme": "http"},
    {"from": "www.example.com", "host_name": "web", "port": 3000, "scheme": "http"}
  ],
  "default_host": "www.example.com",
  "reverse_proxy_port": ":443"
}

Result:

  • Frontend: https://www.example.com
  • API: https://api.example.com
  • Both with automatic HTTPS

⁠Use Case 3: "I need custom SSL certificate (not Let's Encrypt)"

When you need this:

  • Internal/private CA
  • Wildcard certificate
  • EV (Extended Validation) certificate
  • Development with self-signed cert

config.json:

{
  "web_virtual_hosts": [
    {
      "from": "secure.internal.com",
      "scheme": "http",
      "host_name": "backend",
      "port": 8080,
      "server_certificate": {
        "certificate_path": "/certs/internal.pem",
        "private_key_path": "/certs/internal-key.pem"
      }
    }
  ],
  "default_host": "secure.internal.com",
  "reverse_proxy_port": ":443"
}

Docker mount:

docker run -d \
  -p 80:80 -p 443:443 \
  -v ./certs:/certs:ro \
  -v ./config.json:/app/config/config.json:ro \
  janmbaco/go-reverseproxy-ssl:v3

Alternative: Using environment variables with custom certificates:

docker run -d \
  --name reverseproxy \
  -p 80:80 -p 443:443 -p 8081:8081 \
  -e DOMAIN=secure.internal.com \
  -e BACKEND_HOST=backend \
  -e BACKEND_PORT=8080 \
  -e BACKEND_SCHEME=http \
  -e CERT_FILE=/certs/internal.pem \
  -e KEY_FILE=/certs/internal-key.pem \
  -v ./certs:/certs:ro \
  -v certs-storage:/app/certs \
  janmbaco/go-reverseproxy-ssl:v3

Note: When using custom certificates with ENV vars, mount your certificate directory with -v and specify paths with CERT_FILE and KEY_FILE environment variables.


⁠Use Case 4: "I want path-based routing (example.com/api vs example.com/web)"

Scenario: Route /api/* to API service, / to web

Limitation: This proxy routes by domain (not path). Use a single domain with backend path rewriting:

Workaround 1 - Use subdomains (recommended):

  • api.example.com → API service
  • www.example.com → Web service

Workaround 2 - Backend handles routing:

  • Single virtual host → Nginx/Traefik backend that does path routing

Future: Path-based routing planned for v3.1


⁠Configuration

⁠Minimal Configuration
{
  "web_virtual_hosts": [
    {
      "from": "example.com",
      "scheme": "http",
      "host_name": "localhost",
      "port": 8080
    }
  ],
  "default_host": "example.com",
  "reverse_proxy_port": ":443"
}
⁠Full Configuration Reference

See docs/CONFIGURATION.md⁠ for complete reference including:

  • All virtual host types (Web, gRPC-Web)
  • Custom headers
  • Client certificate authentication (mTLS)
  • Backend mTLS
  • Logging configuration
  • Advanced options

Note: grpc_virtual_hosts, grpc_json_virtual_hosts, and ssh_virtual_hosts are deprecated and ignored.


⁠DNS Setup

⁠Required DNS Records

Point your domain to your server's public IP:

# A record
example.com        A    203.0.113.10
api.example.com    A    203.0.113.10
admin.example.com  A    203.0.113.10

# Optional: IPv6
example.com        AAAA 2001:db8::1
⁠Wildcard Subdomain (optional)
*.example.com      A    203.0.113.10

Then any subdomain works: foo.example.com, bar.example.com, etc.


⁠Firewall Configuration

⁠Required Ports
PortProtocolPurpose
80TCPHTTP (redirects to HTTPS, Let's Encrypt challenges)
443TCPHTTPS (main traffic)
8081TCPWeb Configuration UI (optional, can be localhost-only)
⁠Allow Ports

Linux (UFW):

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw reload

Linux (firewalld):

sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload

AWS Security Group:

HTTP   TCP  80   0.0.0.0/0
HTTPS  TCP  443  0.0.0.0/0

Docker Host:

# Ports automatically mapped with -p flag
docker run -p 80:80 -p 443:443 ...

⁠FAQ

⁠How does Let's Encrypt work?
  1. You request https://example.com
  2. Proxy checks for certificate in /app/certs/example.com/
  3. If not found, requests certificate from Let's Encrypt
  4. Let's Encrypt validates ownership via HTTP-01 challenge (port 80)
  5. Certificate issued and cached
  6. Automatically renewed 30 days before expiration

Requirements: DNS must point to your server, port 80 accessible from internet.


⁠Can I use my own certificates?

Yes! Set server_certificate in virtual host config:

{
  "from": "example.com",
  "server_certificate": {
    "certificate_path": "/certs/example.pem",
    "private_key_path": "/certs/example-key.pem"
  }
}

Or set default certificate for all domains:

{
  "default_server_cert": "/certs/default.pem",
  "default_server_key": "/certs/default-key.pem"
}

⁠How do I handle multiple backends for the same domain?

Short answer: Not directly supported.

Alternatives:

  1. Use different subdomains: app1.example.com, app2.example.com
  2. Backend load balancer: Proxy → Nginx/HAProxy → Multiple backends
  3. Wait for v3.1: Load balancing feature planned

⁠What about Docker networking?

Docker Desktop (Mac/Windows):

  • Use host.docker.internal to access host machine

Docker Linux:

  • Use 172.17.0.1 (default bridge gateway)
  • Or use Docker Compose with service names

Docker Compose (recommended):

services:
  proxy:
    networks: [backend]
  app:
    networks: [backend]

Config:

{
  "host_name": "app",  // Service name, not localhost
  "port": 3000
}

⁠How do I check logs?

Docker logs:

docker logs -f reverseproxy

Log files (inside container):

/app/logs/YYYY-MM-DD.log

Log levels (in config.json):

{
  "log_console_level": 4,  // 1=Fatal, 2=Error, 3=Warning, 4=Info, 5=Trace
  "log_file_level": 4
}

⁠Certificate not working?

See docs/TROUBLESHOOTING.md⁠ for detailed troubleshooting, including:

  • Let's Encrypt validation failures
  • DNS propagation issues
  • Firewall blocking port 80
  • Docker networking problems

⁠Can I use this for gRPC-Web (from browser)?

Yes! Use grpc_web_virtual_hosts:

{
  "grpc_web_virtual_hosts": [
    {
      "from": "grpcweb.example.com",
      "scheme": "http",
      "host_name": "grpc-backend",
      "port": 9090
    }
  ],
  "default_host": "grpcweb.example.com",
  "reverse_proxy_port": ":443"
}

Then use gRPC-Web client in JavaScript/TypeScript.


⁠Installation Options

Quick start with ENV vars:

docker run -d \
  --name reverseproxy \
  -p 80:80 -p 443:443 -p 8081:8081 \
  -e DOMAIN=example.com \
  -e BACKEND_HOST=host.docker.internal \
  -e BACKEND_PORT=3000 \
  -v certs:/app/certs \
  janmbaco/go-reverseproxy-ssl:v3

With custom config file:

docker run -d \
  --name reverseproxy \
  -p 80:80 -p 443:443 \
  -v ./configs/config.json:/app/config/config.json:ro \
  -v certs:/app/certs \
  janmbaco/go-reverseproxy-ssl:v3

⁠Docker Compose

See docker-compose.quickstart.yml for complete example.


⁠Build from Source

Requirements: Go 1.25+

git clone https://github.com/janmbaco/go-reverseproxy-ssl.git
cd go-reverseproxy-ssl
go build -o bin/reverseproxy ./cmd/reverseproxy
./bin/reverseproxy --config configs/config.json

⁠Go Module
go get github.com/janmbaco/go-reverseproxy-ssl/v3
package main

import "github.com/janmbaco/go-reverseproxy-ssl/v3/src/infrastructure"

func main() {
    bootstrapper := infrastructure.NewServerBootstrapper("config.json")
    bootstrapper.Start()
}

⁠Features

  • ✓ Automatic HTTPS with Let's Encrypt (HTTP-01 challenge)
  • ✓ HTTP → HTTPS automatic redirect
  • ✓ Multiple protocols: HTTP/1.1, HTTP/2, gRPC-Web
  • ✓ Web Configuration UI for managing virtual hosts
  • ✓ Custom certificates support (BYO cert)
  • ✓ Client certificate authentication (mTLS)
  • ✓ Custom response headers
  • ✓ Lightweight: ~20MB Docker image (Alpine-based)
  • ✓ Zero-downtime certificate renewal
  • ✓ Structured logging with configurable levels

⁠Documentation


⁠What's New in v3.0

  • Clean Architecture: Complete restructure for better maintainability
  • Web Configuration UI: Manage virtual hosts without editing JSON
  • Improved DI: Powered by go-infrastructure v2.0
  • Breaking Changes: See UPGRADE-3.0.md⁠ for migration guide

⁠License

GNU General Public License v3.0 - see LICENSE⁠ file.

Key points:

  • ✓ Free to use, modify, and distribute
  • ✓ Source code must be provided with distributions
  • ✓ Derivative works must also be GPL v3.0
  • ✓ No warranty

Tag summary

Content type

Image

Digest

sha256:3fadc2281…

Size

14.4 MB

Last updated

11 months ago

docker pull janmbaco/go-reverseproxy-ssl