README
ΒΆ
sslly-nginx
A smart Nginx SSL reverse proxy manager that automatically configures SSL certificates and proxies traffic to your local applications.

Features
- π Automatic Configuration: Watches for configuration and SSL certificate changes and 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 Integration: Easy integration with FRP for secure remote access to local services
- π CI/CD Pipeline: Includes GitHub Actions workflows for testing, building, and releasing
How It Works
sslly-nginx is a Go application that runs inside an Nginx Alpine container and manages the Nginx configuration dynamically:
- Configuration Monitoring: Watches
./configs/config.yamlfor changes - Certificate Scanning: Recursively scans
./ssldirectory for certificate files - Nginx Generation: Generates Nginx configuration based on port-to-domain mappings
- Health Checks: Verifies Nginx health after each reload
- Rollback Protection: Maintains last working configuration for automatic recovery
Quick Start
For a quick start & deployment guide, see docs/QUICKSTART.md.
Documentation
- Quick Start Guide - Get started with installation and deployment
- CORS Configuration - Comprehensive CORS setup and best practices
- FRP Integration - Set up FRP for remote access to local services
Configuration
Application Configuration
The configuration file (configs/config.yaml) maps upstream addresses to domain names. It supports multiple formats and advanced features.
CORS Configuration (Optional)
Configure CORS (Cross-Origin Resource Sharing) settings globally or per-domain.
# You can also configure CORS for specific domains:
cors:
'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
Basic Port Mapping Formats
# Proxies to 127.0.0.1:port
1234:
- example.com
- www.example.com
5678:
- api.example.com
# Proxies to 192.168.31.6:1234
192.168.31.6:1234:
- lan.example.com
- local.example.com
# Proxies to example-server.local:8080
example-server.local:8080:
- remote.example.com
# Proxies to IPv6 address 2001:db8::1 port 3000
'[2001:db8::1]:3000':
- ipv6.example.com
[https] prefix)# Proxies to HTTPS backend (prevents "plain HTTP to HTTPS port" errors)
'[https]192.168.50.2:8443':
- secure-backend.example.com
Note: By default, sslly-nginx forwards requests to upstream servers using HTTP. Use the [https] prefix when your upstream server expects HTTPS connections to avoid "400 Bad Request - The plain HTTP request was sent to HTTPS port" errors.
Advanced: Path-based Routing
Route different paths of the same domain to different backends:
# Main site on port 9012
9012:
- shared.example.com
# API endpoints on different server
192.168.50.2:5678/api:
- shared.example.com/api
This generates Nginx configuration with location-based routing:
shared.example.com/apiβ192.168.50.2:5678/apishared.example.com/β127.0.0.1:9012
Configuration Key Formats Summary:
portβ Proxies to127.0.0.1:portvia HTTP (default)ip:portβ Proxies toip:portvia HTTPhostname:portβ Proxies tohostname:portvia HTTP[ipv6]:portβ Proxies to IPv6 address with brackets via HTTP[https]upstreamβ Proxies to upstream via HTTPS (prevents "plain HTTP to HTTPS port" errors)- Example:
[https]192.168.50.2:8443forwards requests using HTTPS
- Example:
upstream/pathβ Adds path routing to the upstream- Example:
192.168.50.2:5678/apiproxies/apirequests to that backend
- Example:
SSL Certificate Structure
Place SSL certificates in the ssl/ directory. The application supports the following naming patterns:
-
Bundle format:
domain_bundle.crtanddomain_bundle.key- Example:
example.com_bundle.crtandexample.com_bundle.key
- Example:
-
Standard format:
domain.crtanddomain.key- Example:
example.com.crtandexample.com.key
- Example:
You can organize certificates in subdirectories:
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:
- Each domain must have exactly one certificate (no duplicates)
- Both
.crtand.keyfiles must exist - Certificates are matched by domain name automatically
- SSL certificates are optional: If no certificate is found for a domain, the service will proxy HTTP traffic directly
- HTTPS to HTTP redirect: If HTTPS is accessed for domains without valid certificates, traffic is redirected to HTTP (301)
HTTP-Only Mode
If you don't have SSL certificates yet or 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:
# config.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/config.yamlor./configs/config.yml) - SSL certificates (
./ssl/**/*)
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
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
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: - SSL_NGINX_HTTP_PORT=9980 # HTTP traffic - SSL_NGINX_HTTPS_PORT=9943 # HTTPS traffic -
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
SSL_NGINX_HTTP_PORT(default:80) β port Nginx listens for HTTP and redirect to HTTPSSSL_NGINX_HTTPS_PORT(default:443) β port Nginx listens for HTTPS
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/
β βββ config.example.yaml # Example configuration
βββ 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/config.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. A nginx container with automatic ssl handling