README
ΒΆ
httpsify - Dynamic HTTPS Reverse Proxy for Local Development
A production-grade Go reverse proxy that provides HTTPS for any local port dynamically via subdomain routing.
https://<PORT>.localhost β http://127.0.0.1:<PORT>
Features
- π Automatic TLS - Use mkcert certificates or auto-generate self-signed certs
- π Dynamic Routing - No manual configuration per port
- π WebSocket Support - Full WebSocket pass-through with connection hijacking
- β‘ Production Quality - Proper timeouts, keep-alive, connection pooling
- π‘οΈ Security - Port denylists, input validation, TLS 1.2+
- π Structured Logging - JSON logs with request IDs, timing, and status codes
- π― Zero Dependencies - Only stdlib + golang.org/x/net for edge cases
Installation
From Source
# Clone the repository
git clone https://github.com/imcanugur/httpsify.git
cd httpsify
# Build
go build -o httpsify ./cmd/httpsify
# Or use make
make build
Using Go Install
go install github.com/imcanugur/httpsify/cmd/httpsify@latest
Quick Start
Trusted Certificates with mkcert
mkcert creates locally-trusted development certificates.
# Install mkcert
# macOS
brew install mkcert
# Linux
sudo apt install libnss3-tools
go install filippo.io/mkcert@latest
# Windows
choco install mkcert
# Install local CA
mkcert -install
# Generate certificates
mkdir -p cert
mkcert -cert-file cert/localhost.pem -key-file cert/localhost-key.pem \
localhost "*.localhost" localtest.me "*.localtest.me" 127.0.0.1 ::1
# Run httpsify
sudo ./httpsify --cert cert/localhost.pem --key cert/localhost-key.pem
Self-Signed Certificates
# Generate and use self-signed certificates
sudo ./httpsify --self-signed
# Certificates are saved to ./cert/ directory
# You'll need to trust ./cert/ca.pem in your browser/system
Why sudo?
Port 443 requires root privileges on Linux/macOS. Alternatives:
# Option 1: Use a high port
./httpsify --listen :8443
# Option 2: Use setcap (Linux only)
sudo setcap CAP_NET_BIND_SERVICE=+eip ./httpsify
./httpsify --listen :443
# Option 3: Use authbind (Linux)
sudo apt install authbind
sudo touch /etc/authbind/byport/443
sudo chmod 500 /etc/authbind/byport/443
sudo chown $USER /etc/authbind/byport/443
authbind --deep ./httpsify
Usage Examples
Laravel Development (Port 8000)
# Terminal 1: Start Laravel
cd my-laravel-app
php artisan serve
# Terminal 2: Start httpsify
sudo ./httpsify
# Access via HTTPS
# https://8000.localhost
React Development (Port 3000)
# Terminal 1: Start React
cd my-react-app
npm start
# Terminal 2: httpsify is already running
# Access via HTTPS
# https://3000.localhost
Vite Development (Port 5173)
# Terminal 1: Start Vite
cd my-vite-app
npm run dev
# Access via HTTPS
# https://5173.localhost
Multiple Services at Once
# All these work simultaneously:
# https://8000.localhost β Laravel API
# https://3000.localhost β React frontend
# https://5173.localhost β Vite dev server
# https://8080.localhost β Go backend
# https://6379.localhost β (blocked - not in allow range by default)
Command-Line Options
Usage: httpsify [options]
Options:
--listen string Listen address (default ":443")
--cert string Path to TLS certificate PEM (default "./cert/localhost.pem")
--key string Path to TLS private key PEM (default "./cert/localhost-key.pem")
--self-signed Generate self-signed certificate if missing
--deny-ports string Comma-separated denied ports/ranges (default "22,25,135-139,445,3389,5900")
--allow-range string Allowed port range (default "1024-65535")
--verbose Enable verbose/debug logging
--access-log Enable access logging (default true)
--version Show version information
Environment Variables
All options can also be set via environment variables:
export HTTPSIFY_LISTEN=":443"
export HTTPSIFY_CERT="./cert/localhost.pem"
export HTTPSIFY_KEY="./cert/localhost-key.pem"
export HTTPSIFY_SELF_SIGNED="true"
export HTTPSIFY_DENY_PORTS="22,25,135-139,445"
export HTTPSIFY_ALLOW_RANGE="1024-65535"
export HTTPSIFY_VERBOSE="true"
export HTTPSIFY_ACCESS_LOG="true"
WebSocket Support
WebSocket connections are fully supported. Example test:
// Connect to a WebSocket server running on port 8080
const ws = new WebSocket('wss://8080.localhost/ws');
ws.onopen = () => {
console.log('Connected!');
ws.send('Hello, WebSocket!');
};
ws.onmessage = (event) => {
console.log('Received:', event.data);
};
WebSocket Test Server (Go)
package main
import (
"fmt"
"net/http"
"golang.org/x/net/websocket"
)
func main() {
http.Handle("/ws", websocket.Handler(func(ws *websocket.Conn) {
var msg string
for {
if err := websocket.Message.Receive(ws, &msg); err != nil {
break
}
fmt.Printf("Received: %s\n", msg)
websocket.Message.Send(ws, "Echo: "+msg)
}
}))
fmt.Println("WebSocket server on :8080")
http.ListenAndServe(":8080", nil)
}
Windows + WSL2 Setup
Running in WSL2
# In WSL2 terminal
cd /path/to/httpsify
# Build
go build -o httpsify ./cmd/httpsify
# Generate certs and run
sudo ./httpsify --self-signed
Accessing from Windows
-
Method 1: Use
.localhostdomain (works automatically in most browsers)- Chrome and Edge support
*.localhostout of the box
- Chrome and Edge support
-
Method 2: Edit Windows hosts file
# Run as Administrator notepad C:\Windows\System32\drivers\etc\hosts # Add these lines (where 172.x.x.x is your WSL IP) 172.x.x.x 8000.localhost 172.x.x.x 3000.localhost -
Get WSL IP
# In WSL hostname -I | awk '{print $1}'
Trusting Self-Signed Certificates on Windows
- Copy
cert/ca.pemfrom WSL to Windows - Double-click the file
- Click "Install Certificate"
- Select "Local Machine" β "Trusted Root Certification Authorities"
Error Responses
httpsify returns helpful JSON errors:
{
"error": "Port 22 is not allowed",
"hint": "This port is either denied or outside the allowed range",
"example": "https://8000.localhost"
}
{
"error": "Connection refused",
"hint": "No service is listening on port 8000",
"example": ""
}
{
"error": "invalid host format: example.com",
"hint": "Use format: https://<port>.localhost",
"example": "https://8000.localhost"
}
Security
Denied Ports (Default)
These ports are blocked by default to prevent accidental exposure:
| Port | Service |
|---|---|
| 22 | SSH |
| 25 | SMTP |
| 135-139 | NetBIOS/SMB |
| 445 | SMB |
| 3389 | RDP |
| 5900 | VNC |
Port Allow Range
By default, only ports 1024-65535 are allowed. To allow privileged ports:
./httpsify --allow-range "80-65535"
TLS Configuration
- Minimum TLS 1.2
- Modern cipher suites only (ECDHE, AES-GCM, ChaCha20)
- Perfect forward secrecy (PFS) enabled
- X25519 preferred for key exchange
Logging
JSON structured logging with request tracking:
{"time":"2024-01-15T10:30:45Z","level":"INFO","msg":"request completed","request_id":"1705315845123456789-1","method":"GET","host":"8000.localhost","target_port":8000,"status":200,"latency":"12.345ms","bytes":1234}
Enable debug logging:
./httpsify --verbose
Troubleshooting
"connection refused" errors
Your backend service isn't running on that port:
# Check what's running
lsof -i :8000 # macOS/Linux
netstat -ano | findstr :8000 # Windows
Certificate warnings in browser
For mkcert users
Make sure you ran mkcert -install to install the local CA.
For self-signed
Import ./cert/ca.pem into your browser/OS trust store.
Chrome:
- Navigate to
chrome://settings/certificates - Go to "Authorities" tab
- Click "Import" and select
ca.pem - Check "Trust this certificate for identifying websites"
Firefox:
- Navigate to
about:preferences#privacy - Scroll to "Certificates" β "View Certificates"
- Go to "Authorities" tab
- Click "Import" and select
ca.pem
"address already in use" error
Another process is using port 443:
# Find the process
sudo lsof -i :443
# Or use a different port
./httpsify --listen :8443
WebSocket connections failing
- Make sure your backend WebSocket server is running
- Check that the backend responds to the WebSocket upgrade handshake
- Enable verbose logging to see what's happening:
./httpsify --verbose
Permission denied (port 443)
See the "Why sudo?" section above for alternatives.
Running as a Service
systemd (Linux)
# Copy the unit file
sudo cp httpsify.service /etc/systemd/system/
# Edit paths as needed
sudo systemctl edit httpsify
# Enable and start
sudo systemctl enable --now httpsify
# Check status
sudo systemctl status httpsify
launchd (macOS)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.httpsify.server</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/httpsify</string>
<string>--cert</string>
<string>/etc/httpsify/localhost.pem</string>
<string>--key</string>
<string>/etc/httpsify/localhost-key.pem</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
</dict>
</plist>
Testing
# Run all tests
go test ./...
# Run with verbose output
go test -v ./...
# Run specific package tests
go test -v ./internal/config/
go test -v ./internal/proxy/
Project Structure
httpsify/
βββ cmd/
β βββ httpsify/
β βββ main.go # CLI entry point
βββ internal/
β βββ config/
β β βββ config.go # Configuration parsing
β β βββ config_test.go # Config tests
β βββ logging/
β β βββ logging.go # Structured logging
β βββ proxy/
β β βββ proxy.go # Reverse proxy handler
β β βββ proxy_test.go # Proxy tests
β βββ tls/
β βββ tls.go # TLS configuration
βββ cert/ # Generated certificates (gitignored)
βββ go.mod
βββ go.sum
βββ Makefile
βββ README.md
βββ httpsify.service # systemd unit template
Contributing
- Fork the repository
- Create a feature branch
- Make your changes
- Run tests:
go test ./... - Submit a pull request
License
MIT License - see LICENSE file for details.