This guide explains how to set up NGINX with SSL/TLS for OpenTranscribe, enabling:
- Browser microphone recording (required for HTTPS - browsers block mic access over HTTP)
- Secure access from any device on your network
- Production deployments with custom domains
NGINX is required for PKI certificate-based authentication. The --with-pki flag loads docker-compose.pki.yml which configures NGINX to verify client certificates before forwarding requests to the backend.
# Start with PKI (production only — dev mode cannot handle mTLS)
./opentr.sh start prod --build --with-pki
# Access at https://localhost:5182
# Requires a valid client certificate (.p12 file)Client certificates are generated by scripts/pki/ and stored in scripts/pki/test-certs/clients/. For production deployments, replace test certificates with your organization's CA-signed certificates.
Note: PKI mode only works with the prod environment overlay. The dev Vite server cannot perform mTLS handshakes.
Modern browsers enforce strict security policies. The getUserMedia() API (used for microphone access) only works in:
localhostconnections (development exception)- HTTPS connections (production requirement)
If you try to use microphone recording over HTTP from another device or IP address, browsers will block it. This is documented in GitHub Issue #72.
For homelabs and small businesses, self-signed certificates are the fastest way to get HTTPS working.
# Navigate to OpenTranscribe directory
cd /path/to/OpenTranscribe
# Generate certificates with auto-detected IP addresses
./scripts/generate-ssl-cert.sh opentranscribe.local --auto-ip
# Or specify IP addresses manually
./scripts/generate-ssl-cert.sh opentranscribe.local --ip 192.168.1.100 --ip 10.0.0.50This creates:
nginx/ssl/server.crt- SSL certificatenginx/ssl/server.key- Private key
Edit your .env file and add/uncomment:
NGINX_SERVER_NAME=opentranscribe.localOption A: Router DNS (Recommended)
Add a DNS entry in your router pointing opentranscribe.local to your server's IP address.
Option B: Local Hosts File
On each client device, add to /etc/hosts (Linux/Mac) or C:\Windows\System32\drivers\etc\hosts (Windows):
192.168.1.100 opentranscribe.local
Replace 192.168.1.100 with your server's actual IP address.
Multi-Service Homelab Setup
If you run multiple services on the same server, you can add multiple hostnames pointing to the same IP:
192.168.1.100 opentranscribe.local
192.168.1.100 nextcloud.local
192.168.1.100 jellyfin.local
192.168.1.100 homeassistant.local
Each service's NGINX/reverse proxy uses the Host header to route traffic correctly. This is a standard homelab configuration that works reliably.
Pi-hole / AdGuard Home / Local DNS Server
If you use a local DNS server (Pi-hole, AdGuard Home, Unbound, etc.), add an A record:
- Domain:
opentranscribe.local - IP Address: Your server's IP (e.g.,
192.168.1.100)
./opentr.sh start devThe script automatically detects NGINX_SERVER_NAME and includes the NGINX overlay.
On each device that will access OpenTranscribe, you need to trust the self-signed certificate.
- Copy
nginx/ssl/server.crtto the client device - Double-click the
.crtfile - Click Install Certificate → Local Machine
- Select Place all certificates in the following store
- Browse and select Trusted Root Certification Authorities
- Complete the wizard and restart your browser
- Copy
nginx/ssl/server.crtto the Mac - Double-click to open in Keychain Access
- Find the certificate in the list, double-click it
- Expand Trust section
- Set When using this certificate to Always Trust
- Close the window and enter your password to confirm
- Open Chrome and go to
chrome://settings/certificates - Click Authorities tab → Import
- Select
nginx/ssl/server.crt - Check Trust this certificate for identifying websites
- Click OK and restart Chrome
- Open Firefox and go to
about:preferences#privacy - Scroll to Certificates → View Certificates
- Click Authorities tab → Import
- Select
nginx/ssl/server.crt - Check Trust this CA to identify websites
- Click OK and restart Firefox
- Email or AirDrop
server.crtto your iOS device - Open the file → "Profile Downloaded" notification appears
- Go to Settings → General → VPN & Device Management
- Tap the downloaded profile and install it
- Go to Settings → General → About → Certificate Trust Settings
- Enable full trust for the OpenTranscribe certificate
- Copy
server.crtto your Android device - Go to Settings → Security → Encryption & credentials
- Tap Install a certificate → CA certificate
- Select the
.crtfile and confirm installation
Open your browser and go to:
https://opentranscribe.local
You can now use microphone recording from any device on your network!
For production deployments with a public domain, use Let's Encrypt for free, trusted certificates.
- A domain name pointing to your server (e.g.,
transcribe.example.com) - Ports 80 and 443 accessible from the internet
# Ubuntu/Debian
sudo apt install certbot
# CentOS/RHEL
sudo dnf install certbot
# macOS
brew install certbotStop OpenTranscribe if running:
./opentr.sh stopGenerate certificates (standalone mode):
sudo certbot certonly --standalone -d transcribe.example.comOr with DNS validation (if ports 80/443 aren't available):
sudo certbot certonly --manual --preferred-challenges dns -d transcribe.example.com# Create nginx/ssl directory
mkdir -p nginx/ssl
# Link Let's Encrypt certificates
sudo ln -sf /etc/letsencrypt/live/transcribe.example.com/fullchain.pem nginx/ssl/server.crt
sudo ln -sf /etc/letsencrypt/live/transcribe.example.com/privkey.pem nginx/ssl/server.key
# Fix permissions for Docker
sudo chmod 644 /etc/letsencrypt/live/transcribe.example.com/fullchain.pem
sudo chmod 600 /etc/letsencrypt/live/transcribe.example.com/privkey.pemEdit .env:
NGINX_SERVER_NAME=transcribe.example.com
NGINX_CERT_FILE=/etc/letsencrypt/live/transcribe.example.com/fullchain.pem
NGINX_CERT_KEY=/etc/letsencrypt/live/transcribe.example.com/privkey.pem# Test renewal
sudo certbot renew --dry-run
# Add to crontab for automatic renewal
echo "0 0 1 * * certbot renew --quiet && docker compose restart nginx" | sudo tee -a /etc/crontab./opentr.sh start prod| Variable | Default | Description |
|---|---|---|
NGINX_SERVER_NAME |
(none) | Hostname for NGINX. Setting this enables the NGINX overlay. |
NGINX_HTTP_PORT |
80 |
HTTP port (redirects to HTTPS) |
NGINX_HTTPS_PORT |
443 |
HTTPS port |
NGINX_CERT_FILE |
./nginx/ssl/server.crt |
Path to SSL certificate |
NGINX_CERT_KEY |
./nginx/ssl/server.key |
Path to SSL private key |
OpenTranscribe/
├── docker-compose.nginx.yml # NGINX service definition
├── nginx/
│ ├── site.conf.template # NGINX configuration template
│ └── ssl/
│ ├── server.crt # SSL certificate
│ └── server.key # SSL private key
└── scripts/
└── generate-ssl-cert.sh # Certificate generation script
All services are accessed through a single HTTPS port when NGINX is active, providing unified access and eliminating the need to expose multiple ports.
| Path | Destination | Description |
|---|---|---|
/ |
Frontend (port 8080) | Svelte SPA |
/docs/ |
Docs (port 8080) | Embedded documentation site |
/api/ws |
Backend WebSocket | Real-time notifications |
/api/ |
Backend REST API | All API endpoints |
/flower/ |
Flower (port 5555) | Celery task monitoring dashboard |
/minio/ |
MinIO Console (port 9001) | S3 storage management |
/s3/ |
MinIO API (port 9000) | Direct S3 operations |
The Flower dashboard (/flower/) is proxied through NGINX, so task monitoring is available at the same HTTPS address as the application without exposing port 5555 separately.
The script checks for certificates before starting. Generate them first:
./scripts/generate-ssl-cert.sh your-hostname.local --auto-ipThis is expected with self-signed certificates. You have two options:
- Trust the certificate on each device (recommended for homelab)
- Use Let's Encrypt for publicly trusted certificates (recommended for production)
Check if NGINX container is running:
docker compose ps nginx
docker compose logs nginx- Verify you're using HTTPS (not HTTP)
- Check browser console for errors
- Ensure the certificate is trusted on the device
- Try in an incognito/private window
For self-signed certificates:
./scripts/generate-ssl-cert.sh your-hostname.local --auto-ip
./opentr.sh restart-allFor Let's Encrypt:
sudo certbot renew
docker compose restart nginxTo customize the NGINX configuration:
- Edit
nginx/site.conf.template - Restart NGINX:
docker compose restart nginx
Common customizations:
- Add HTTP Basic Authentication for Flower/MinIO
- Adjust client body size limits
- Add custom headers
- Configure rate limiting
Example: Adding Basic Auth for Flower
location /flower/ {
proxy_pass http://flower:5555/flower/;
auth_basic "Flower Dashboard";
auth_basic_user_file /etc/nginx/.htpasswd;
}- Suitable for: Homelab, internal networks, development
- Not suitable for: Public-facing production deployments
- Warning: Users will see browser security warnings until they trust the certificate
- Suitable for: Production deployments with public domains
- Benefits: Free, automatically trusted by all browsers
- Requirement: Domain must be publicly accessible for validation
- Keep private keys secure (never commit to git)
- Use strong key sizes (2048-bit minimum, 4096-bit recommended)
- Renew certificates before expiration
- Monitor certificate expiration dates