Self-Hosting
UniAuth is designed to be self-hosted, giving you full control over your identity infrastructure. This guide covers system requirements, configuration, database setup, and production deployment.
System Requirements
| Component | Minimum | Recommended |
|---|---|---|
| Node.js | 20.x | 22.x LTS |
| PostgreSQL | 15 | 16+ |
| Redis | Optional | 7.x (for distributed rate limiting) |
| RAM | 512 MB | 1 GB+ |
| Disk | 1 GB | 10 GB+ (for logs and database) |
Environment Variables
Create a .env.local file in the project root with the following variables:
Required
| Variable | Description | Example |
|---|---|---|
DB_HOST | PostgreSQL hostname | localhost |
DB_PORT | PostgreSQL port | 5432 |
DB_USER | Database user | postgres |
DB_PASSWORD | Database password | your-secure-password |
DB_NAME | Database name | uniauth_db |
JWT_SECRET | Secret for signing JWTs (min 32 chars) | a-long-random-string... |
ENCRYPTION_KEY | AES-256-GCM key (exactly 64 hex chars) | 0a1b2c3d...64 hex chars |
HOST | Public-facing URL (used for OAuth redirects, WebAuthn RP ID) | https://auth.example.com |
Optional: Email (SMTP)
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=your-smtp-user
SMTP_PASS=your-smtp-password
SMTP_SECURE=false # true for port 465
[email protected]When SMTP is not configured, emails are logged to the console. This is useful for development but should be configured for production.
Optional: Social Login (OAuth)
# Google OAuth
GOOGLE_CLIENT_ID=your-google-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your-google-client-secret
# GitHub OAuth
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secretOptional: SMS 2FA (Twilio)
TWILIO_ACCOUNT_SID=your-twilio-sid
TWILIO_AUTH_TOKEN=your-twilio-auth-token
TWILIO_PHONE_NUMBER=+1234567890Optional: Redis
REDIS_URL=redis://localhost:6379Redis enables distributed rate limiting across multiple instances. Without Redis, rate limiting uses an in-memory store (suitable for single-instance deployments).
Generating Secrets
Use the following commands to generate cryptographically secure secrets:
# Generate ENCRYPTION_KEY (64 hex chars = 32 bytes = AES-256)
openssl rand -hex 32
# Generate JWT_SECRET
openssl rand -base64 48
# Alternatively, using Node.js:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"Database Setup
UniAuth uses PostgreSQL. Create the database and apply the schema:
# Create the database
psql -U postgres -c "CREATE DATABASE uniauth_db;"
# Apply the main schema
psql -U postgres -d uniauth_db -f scripts/pg-schema.sql
# Apply migrations (in order)
psql -U postgres -d uniauth_db -f scripts/migration-crypto.sql
psql -U postgres -d uniauth_db -f scripts/migration-hardening.sqlThe schema creates all required tables including users, sessions, oauth_clients, roles, system_settings, and others. The connection pool is managed by lib/db.ts using the pg driver.
Quick Start
# Clone the repository
git clone https://github.com/your-org/uniauth.git
cd uniauth
# Install dependencies
npm install
# Copy and edit environment variables
cp .env.example .env.local
# Edit .env.local with your values
# Build for production
npm run build
# Start the server (default port 4000)
npm start
# Or run in development mode
npm run devThe server listens on port 4000 by default. You can change this with the PORT environment variable.
Running Behind a Reverse Proxy
In production, UniAuth should run behind a reverse proxy that handles TLS termination. Below are example configurations for common web servers.
Nginx
server {
listen 443 ssl http2;
server_name auth.example.com;
ssl_certificate /etc/ssl/certs/auth.example.com.pem;
ssl_certificate_key /etc/ssl/private/auth.example.com.key;
# Security headers (UniAuth adds its own, but these are belt-and-suspenders)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
location / {
proxy_pass http://127.0.0.1:4000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket support (for dev hot reload)
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
# Redirect HTTP to HTTPS
server {
listen 80;
server_name auth.example.com;
return 301 https://$host$request_uri;
}Apache
<VirtualHost *:443>
ServerName auth.example.com
SSLEngine on
SSLCertificateFile /etc/ssl/certs/auth.example.com.pem
SSLCertificateKeyFile /etc/ssl/private/auth.example.com.key
ProxyPreserveHost On
ProxyPass / http://127.0.0.1:4000/
ProxyPassReverse / http://127.0.0.1:4000/
RequestHeader set X-Forwarded-Proto "https"
RequestHeader set X-Forwarded-Port "443"
</VirtualHost>
<VirtualHost *:80>
ServerName auth.example.com
Redirect permanent / https://auth.example.com/
</VirtualHost>Important: HOST Environment Variable
When running behind a reverse proxy, the HOST environment variable must be set to your public-facing URL (e.g., https://auth.example.com). UniAuth uses this for OAuth redirect URLs, WebAuthn Relying Party ID, and OIDC Discovery. Without it, URLs will resolve to localhost:4000.
HTTPS / TLS Setup
TLS is strongly recommended for production. UniAuth relies on HTTPS for:
- Secure cookie transmission (
Secureflag on auth cookies) - HSTS headers (automatically enabled in production)
- WebAuthn (passkeys require a secure context)
- OAuth redirect URI validation
For free TLS certificates, use Let's Encrypt with certbot or your hosting provider's built-in certificate management.
Production Checklist
Before going live, verify the following:
- HOST is set to your public URL (e.g.,
https://auth.example.com). - ENCRYPTION_KEY is generated using
openssl rand -hex 32(64 hex chars). Back up this key securely — losing it means losing access to encrypted data. - JWT_SECRET is strong — at least 32 random characters.
- HTTPS is enabled via a reverse proxy with a valid TLS certificate.
- HSTS is active — UniAuth sends HSTS headers automatically in production.
- SMTP is configured for email verification, password resets, and email-based 2FA.
- Database backups are scheduled — regular
pg_dumpexports. - Rate limiting works — test that login rate limits (10 per 15 min) are enforced.
- Firewall rules — only expose ports 80/443 publicly. PostgreSQL (5432) and Redis (6379) should be internal only.
- Admin account is set up — the first user registered can be promoted to admin via the database or by setting the email in the admin configuration.