README
¶
sslly-nginx
A smart Nginx SSL reverse proxy manager that automatically configures SSL certificates and proxies traffic to your local applications.
I HATE writing Nginx config, that's why this project was born.
Just tell this tool the port and domain, and let it handle the rest.Robert He

Features
- Simple Rules: Just map port to domains in a YAML file, no more Nginx config writing
- Automatic Configuration: Watches for configuration and SSL certificate change, automatically reloads Nginx
- SSL Management: Automatically scans and maps SSL certificates to domains
- Hot Reload: Updates Nginx configuration without downtime when files change
- Error Recovery: Maintains the last working configuration and rolls back on failures
- Docker Ready: Runs as a containerized service with Docker Compose
- FRP Friendly: Easy integration with FRP for secure remote access to local services
Supported Features
- HTTP and HTTPS proxying
- Automatic HTTP → HTTPS redirection for domains with valid certificates
- TCP and UDP stream forwarding
- CORS configuration (optional)
- Custom log levels and formats (optional)
- WebSocket support
- Static site hosting
Quick Start
One-Command Setup
# Set up working directory
export SSLLY_NGINX_HOME=$HOME/sslly-nginx
mkdir -p $SSLLY_NGINX_HOME && cd $SSLLY_NGINX_HOME
# Download Docker Compose configuration
curl -fsSL https://raw.githubusercontent.com/hnrobert/sslly-nginx/main/docker-compose.yml -o docker-compose.yml
# Start the service
docker-compose up -d
The service will start with default configuration and create configs/ and ssl/ directories.
Customize Configuration
Edit configs/proxy.yaml to add your routes:
# View logs
docker-compose logs -f
# Stop service
docker-compose down
Add SSL Certificates
Drop certificate files into the ssl/ directory:
ssl/
├── example.com.crt
├── example.com.key
└── api.example.com_bundle.crt
Documentation
- Configuration Reference - Complete configuration format and rules
- CORS Configuration - Comprehensive CORS setup and best practices
- FRP Integration - Set up FRP for remote access to local services
Configuration
Configuration Format Summary
upstream_key:
- listener_key_1
- listener_key_2
upstream_key Format
<upstream_protocol>domain:port/routes
or for static sites:
static_route//additional/routes
| Component | Required | Default | Description |
|---|---|---|---|
upstream_protocol |
No | http |
https, tcp, udp (omit for http) |
domain |
No | 127.0.0.1 |
IP or hostname (IPv6: [::1]) |
port |
No | Protocol default | Port number |
routes |
No | - | URL path routing |
static_route |
- | - | Path representing www root location in filesystem starting with / or . or .. (. = /app) |
listener_key Format
<listen_protocol>listened_server_name|listened_port
| Component | Required | Default | Description |
|---|---|---|---|
listen_protocol |
No | Smart mode | http, https, tcp, udp |
listened_server_name |
No | All interfaces | Server name (domain) |
listened_port |
No | Env var ports | Listen port |
Quick Examples
# HTTP proxy to localhost:8080
8080:
- example.com
# HTTPS upstream
<https>api.secure.com:
- example.com
# TCP forwarding
<tcp>9122:
- 8122
# Static site (relative path, . = /app)
./static:
- static.example.com
# Static site (parent path, .. = /)
../data:
- data.example.com
# Static with route path (using //)
/app/static//docs:
- docs.example.com
For complete format specification and advanced examples, see Configuration Reference.
Optional Configuration Files
CORS Configuration
Configure CORS (Cross-Origin Resource Sharing) settings globally or per-domain.
api.example.com:
allow_origin: 'https://app.example.com'
allow_methods: [GET, POST, PUT, DELETE, OPTIONS]
allow_headers: [Content-Type, Authorization]
allow_credentials: true
For more please check CORS Configuration for comprehensive CORS setup guide and best practices examples.
SSL Certificate Structure
Place SSL certificates in the ssl/ directory. The application automatically matches certificate files (.crt) with their corresponding private key files (.key) based on the domain information contained within the SSL certificates themselves.
ssl/
├── production/
│ ├── example.com_bundle.crt
│ └── example.com_bundle.key
├── staging/
│ ├── staging.example.com.crt
│ └── staging.example.com.key
└── api.example.com.crt
└── api.example.com.key
Important Notes:
- Duplicate certificates are allowed for each domain, If multiple pairs of certificate+key are found, the farthest expiration time is selected.
- Certificate and key files are optional (a domain without a matched cert/key will be served over HTTP)
- SSL certificates are optional: If no certificate is found for a domain, the service will proxy HTTP traffic directly to your applications
- HTTPS to HTTP redirect: If HTTPS is accessed for domains without valid certificates, traffic is redirected to HTTP (301)
Backup & Crash Recovery
To make hot-reloads safer, sslly-nginx keeps a persistent on-disk snapshot of the last known-good configuration.
- Backup folder:
configs/.sslly-backups/ - Snapshot content:
configs/+ssl/+ generated/etc/nginx/nginx.conf - Runtime cache: The currently used cert/key files are copied into
configs/.sslly-runtime/current/and nginx.conf only references that cache, so edits underssl/won't affect the running nginx process until a successful reload.
Crash detection: If the previous run died mid-reload, the next start detects the unfinished reload and automatically restores the last known-good snapshot.
HTTP-Only Mode
If you don't have SSL certificates yet but want to serve some domains over HTTP only:
- The application will automatically detect missing certificates
- Domains without certificates will be served over HTTP (no redirect)
- Domains with certificates will use HTTPS with automatic HTTP → HTTPS redirect
- HTTPS fallback: If someone accesses HTTPS for a domain without a valid certificate, they'll be redirected to HTTP with a 301 status
- You can mix HTTP and HTTPS domains in the same configuration
Example scenario:
# proxy.yaml
1234:
- secure.example.com # Has certificate → HTTPS
- dev.example.com # No certificate → HTTP only
Features in Detail
Automatic HTTPS Redirect
When SSL certificates are detected:
- All HTTP traffic for domains with certificates is automatically redirected to HTTPS
- HTTPS traffic for domains without certificates is redirected to HTTP (301) to avoid certificate errors
- If no certificates are found for any domain, HTTP traffic is proxied directly to your applications
Hot Reload
The application watches for changes in:
- Configuration files (
./configs/proxy.yaml, optional./configs/cors.yaml,./configs/logs.yaml) - SSL certificates (
./ssl/**/*)
Note: internal state folders under configs/ (like configs/.sslly-backups/ and configs/.sslly-runtime/) are ignored by the watcher to avoid feedback loops.
When changes are detected:
- New configuration is generated
- Nginx configuration is tested
- If valid, Nginx is reloaded
- If invalid, the previous working configuration is restored (including on-disk
configs/+ssl/contents)
Logs: Domain Summary
On startup and after every successful reload, the service prints a single domain summary instead of logging domain status one-by-one:
Matched:(INFO) domains with a valid certificate+key pair (labeled "SSL")No-cert:(WARN) domains with no matched certificate+key (served over HTTP)Expired:(WARN) domains with a matched certificate+key but the certificate is expiredMulti-certs:(WARN) domains where multiple certificate candidates were found; the selected cert path is shown along with the ignored count.
Error Handling
- Initial Startup:
- If configuration is invalid, the service stops
- Missing SSL certificates are not an error - service runs in HTTP-only mode
- Runtime Errors: If reload fails, the application:
- Logs detailed error messages
- Restores the last working configuration
- Continues running with previous settings
Testing
Run unit tests:
go test ./...
Run tests with coverage:
go test ./... -coverprofile=coverage.out
go tool cover -func=coverage.out
go tool cover -html=coverage.out -o coverage.html
WebSocket Support
The generated Nginx configuration includes WebSocket support for all proxied applications.
Advanced Proxy Features
The reverse proxy includes optimized settings for various applications:
- Large File Upload: Supports files up to 100MB by default
- Correct Host Header: Uses
$hostto preserve the original request hostname (critical for apps like qBittorrent, OnlineJudge) - Proxy Headers: Includes all standard headers:
Host: Original request hostname (e.g.,torrent.hnrobert.space)X-Real-IP: Client's real IP addressX-Forwarded-For: Full proxy chainX-Forwarded-Host: Original Host headerX-Forwarded-Proto: Original protocol (http/https)
- Cookie Security: Automatically sets Secure flag for cookies when using HTTPS
- Timeouts: Configured with 60s timeouts for connect/send/read operations
- Proxy Buffering: Optimized buffer settings for better performance
These settings work well with applications like:
- qBittorrent (WebUI)
- Portainer (Docker management)
- Jellyfin (Media streaming)
- Home Assistant (Smart home)
- OnlineJudge (Competitive programming)
- And most other web applications
FRP Integration
sslly-nginx integrates seamlessly with FRP (Fast Reverse Proxy) to expose your local services through remote servers, enabling secure remote access to your applications from anywhere.
Key Benefits
- Secure Remote Access: Access your local applications from anywhere via HTTPS
- Custom Domains: Use your own domain names instead of IP addresses
- SSL Management: SSL certificates configured locally for domain-based routing
- Flexible Port Configuration: Change HTTP/HTTPS ports to avoid conflicts with FRP
Quick Setup
-
Configure Ports: Modify
docker-compose.ymlto use non-standard ports:environment: - SSLLY_DEFAULT_HTTP_LISTEN_PORT=9980 # HTTP traffic - SSLLY_DEFAULT_HTTPS_LISTEN_PORT=9943 # HTTPS trafficNote: The legacy environment variables
SSL_NGINX_HTTP_PORTandSSL_NGINX_HTTPS_PORTare also supported for backward compatibility. -
Setup FRP Client: Create
frpc.toml:serverAddr = "your-frp-server.com" serverPort = 7000 auth.method = "token" auth.token = "your-secure-token" # HTTPS proxy - handles SSL/TLS traffic [[proxies]] name = "sslly-nginx-https" type = "https" localIP = "127.0.0.1" localPort = 9943 customDomains = ["*.yourdomain.com", "yourdomain.com"] # HTTP proxy - handles plain HTTP and auto-redirects [[proxies]] name = "sslly-nginx-http" type = "http" localIP = "127.0.0.1" localPort = 9980 customDomains = ["*.yourdomain.com", "yourdomain.com"] -
Start Services: Run both FRP client and sslly-nginx
For detailed FRP integration guide, see docs/FRP.md.
Development
Build Locally
# Build the binary
make build
# Run tests
make test
# Run tests with coverage
make test-coverage
# Format code
make fmt
# Run linter
make lint
Build Docker Image
# Build image
make docker-build
# Or use Docker directly
docker build -t sslly-nginx:latest .
Run Locally (without Docker)
# Note: Requires Nginx installed on your system
make run
CI/CD Workflows
The project includes three GitHub Actions workflows:
1. CI Workflow (ci.yml)
- Triggers: All branch pushes and pull requests
- Actions:
- Build the application
- Run tests
- Run linter and format checks
- No Docker image is built
2. Docker Build Workflow (docker-build.yml)
- Triggers: Pushes to
mainanddevelopbranches - Actions:
- Run tests
- Build Docker image
- Push to
ghcr.io
- Tags:
mainbranch →latesttagdevelopbranch →developtag
3. Release Workflow (release.yml)
- Triggers:
- Git tag push (e.g.,
v1.0.0) - Manual workflow dispatch
- Git tag push (e.g.,
- Actions:
- Create tag (if workflow_dispatch)
- Run tests
- Build and push Docker image with version tag
- Create GitHub release
Docker Compose Configuration
The docker-compose.yml is configured with:
- Network Mode:
host- Uses host networking for direct port access - Restart Policy:
on-failure- Stops on errors, auto-starts on system boot - Volumes:
./configs:/app/configs:ro- Configuration (read-only)./ssl:/app/ssl:ro- SSL certificates (read-only)
Environment Variables
SSLLY_DEFAULT_HTTP_LISTEN_PORT(default:80) — port Nginx listens for HTTP and redirect to HTTPSSSLLY_DEFAULT_HTTPS_LISTEN_PORT(default:443) — port Nginx listens for HTTPS
Note: The legacy environment variables
SSL_NGINX_HTTP_PORTandSSL_NGINX_HTTPS_PORTare still supported for backward compatibility but are deprecated.
Viewing Logs
All logs (application + nginx access/error logs) are forwarded to Docker's log collector:
# View all logs
docker-compose logs -f
# View only application logs
docker-compose logs -f sslly-nginx
# View last 100 lines
docker-compose logs --tail=100 sslly-nginx
Nginx access and error logs are automatically forwarded to stdout/stderr and visible via docker logs
Project Structure
sslly-nginx/
├── cmd/
│ └── sslly-nginx/
│ └── main.go # Application entry point
├── internal/
│ ├── app/
│ │ └── app.go # Application logic
│ ├── config/
│ │ ├── config.go # Configuration loader
│ │ └── config_test.go
│ ├── nginx/
│ │ └── nginx.go # Nginx management
│ ├── ssl/
│ │ ├── ssl.go # Certificate scanner
│ │ └── ssl_test.go
│ └── watcher/
│ └── watcher.go # File system watcher
├── .github/
│ └── workflows/
│ ├── ci.yml # CI pipeline
│ ├── docker-build.yml # Docker build pipeline
│ └── release.yml # Release pipeline
├── configs/
│ ├── proxy.yaml # Proxy mappings (required)
│ ├── cors.yaml # Optional CORS settings
│ ├── logs.yaml # Optional log settings
│ ├── proxy.example.yaml # Example proxy mappings
│ ├── cors.example.yaml # Example CORS settings
│ └── logs.example.yaml # Example log settings
├── ssl/
│ └── README.md # SSL certificate guide
├── Dockerfile # Docker image definition
├── docker-compose.yml # Docker Compose configuration
├── Makefile # Build automation
├── go.mod # Go module definition
└── README.md # This file
Logging
The application logs important events:
- Configuration changes detected
- Certificate scanning results
- Nginx reload success/failure
- Error details with recovery actions
Logs can be viewed with:
docker-compose logs -f
Troubleshooting
Container Stops Immediately
Cause: Invalid configuration or missing certificates
Solution:
- Check logs:
docker-compose logs - Verify
configs/proxy.yamlexists and is valid YAML - Ensure all domains have matching certificates in
ssl/
Certificate Not Found
Cause: Certificate file naming doesn't match expected patterns
Solution:
- Check certificate files follow naming pattern:
domain.crt/keyordomain_bundle.crt/key - Ensure both
.crtand.keyfiles exist - Check logs for certificate scanning results
Nginx Fails to Reload
Cause: Configuration error or certificate issues
Solution:
- Application automatically rolls back to last working configuration
- Check logs for specific error messages
- Fix the configuration or certificate issue
- Changes will be automatically detected and reloaded
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
See LICENSE file for details.
Support
For issues and questions, please use the GitHub Issues page.