How to Secure FreeLLMAPI When Exposing It on a Network
Secure FreeLLMAPI by encrypting provider API keys with AES-256-GCM, binding the service to localhost by default, enforcing dashboard authentication with bcrypt-hashed passwords, and terminating TLS through a reverse proxy.
FreeLLMAPI acts as a single-user proxy that forwards requests to multiple LLM providers. When you expose this service on a network—whether on a LAN, cloud instance, or behind a reverse proxy—you must harden it across multiple layers to prevent credential theft and unauthorized access. The tashfeenahmed/freellmapi repository implements several security controls that you configure through environment variables and deployment practices.
Encrypt Provider API Keys with AES-256-GCM
FreeLLMAPI stores upstream provider API keys encrypted rather than in plain text. The encryption system uses AES-256-GCM with a 64-character hexadecimal ENCRYPTION_KEY that you must generate for production deployments.
In server/src/lib/crypto.ts, the application validates the key length at startup (lines 19–26) and uses it to encrypt all credentials stored in the api_keys table. The encryption key is loaded from the environment and cached for the process lifetime.
Generate a production-ready key using Node.js:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
Store this value in the ENCRYPTION_KEY environment variable. Never commit this key to version control.
Control Network Binding and CORS
By default, FreeLLMAPI binds to 127.0.0.1 to prevent accidental public exposure. You configure network behavior through environment variables defined in .env.example.
To expose the service on a trusted LAN, set HOST_BIND=0.0.0.0. Restrict dashboard access by whitelisting origins in DASHBOARD_ORIGINS:
HOST_BIND=0.0.0.0
DASHBOARD_ORIGINS="https://my-dashboard.example.com,https://admin.example.com"
The dashboard routes in server/src/routes/auth.ts enforce these CORS restrictions. Keep the default localhost binding unless you explicitly intend to serve external traffic.
Authenticate Dashboard and Proxy Requests
FreeLLMAPI uses two distinct authentication layers: session-based access for the administrative dashboard and unified API keys for the proxy endpoint.
Dashboard Authentication (server/src/services/auth.ts) implements email/password accounts with bcrypt password hashing. Sessions are identified by SHA-256 hashes of 32-byte random tokens. Create an admin user via the internal API or database seeding:
import { createUser } from './server/src/services/auth.js';
const user = createUser('[email protected]', 'StrongPa$$w0rd');
console.log('Created user', user.id);
Unified API Keys (server/src/routes/proxy.ts) protect the /v1 proxy endpoint. Clients must present the key as Authorization: Bearer <key> or X-API-Key. The server validates the key against the encrypted api_keys table before routing to upstream providers.
Generate a unified key via the dashboard API:
curl -X POST http://localhost:3001/api/keys \
-H "Authorization: Bearer $(cat ~/.session-token)" \
-d '{"platform":"openai","label":"production-key"}'
The response returns the raw key once; store it securely on the client side.
Enforce Rate Limiting and Cooldowns
The rate limiting service (server/src/services/ratelimit.ts) implements sliding-window counters for both per-minute and per-day limits. Counters persist to SQLite, allowing limits to survive process restarts.
When a request returns 429, 402, or 403 from an upstream provider, the system triggers a cooldown stored in rate_limit_cooldowns. Configure the per-minute limit via PROXY_RATE_LIMIT_RPM:
PROXY_RATE_LIMIT_RPM=120
Set this to 0 to disable rate limiting entirely, though this is not recommended for production.
Terminate TLS and Audit Logs
FreeLLMAPI serves plain HTTP and expects you to run it behind an HTTPS reverse proxy such as Nginx or Caddy. The reverse proxy terminates TLS and forwards traffic to the internal port specified by the PORT variable (default 3001).
For audit logging, the application redacts sensitive data in server/src/lib/error-redaction.ts. Request logs store only hashed session IDs and masked API keys, ensuring that compromised logs do not leak credentials.
Production Deployment Checklist
Deploy FreeLLMAPI securely using Docker with the following configuration:
# Generate encryption key
export ENCRYPTION_KEY=$(node -e "console.log(require('crypto').randomBytes(32).toString('hex'))")
# Create environment file
cat > .env <<EOF
ENCRYPTION_KEY=$ENCRYPTION_KEY
PORT=3001
HOST_BIND=127.0.0.1
PROXY_RATE_LIMIT_RPM=120
DASHBOARD_ORIGINS="https://dashboard.example.com"
PROXY_URL="http://corporate-proxy:3128"
EOF
# Run container
docker run -d \
-p 3001:3001 \
-v $(pwd)/data:/app/data \
--env-file .env \
--name freellmapi \
ghcr.io/tashfeenahmed/freellmapi:latest
When using an outbound proxy for provider traffic, configure PROXY_URL with an HTTP or SOCKS5 URL. The proxyFetch function in server/src/lib/proxy.ts routes requests through this proxy while supporting per-platform bypass lists.
Make LLM requests using the unified key:
curl -X POST http://localhost:3001/v1/chat/completions \
-H "Authorization: Bearer <UNIFIED_KEY>" \
-H "Content-Type: application/json" \
-d '{
"model":"gpt-3.5-turbo",
"messages":[{"role":"user","content":"Hello"}]
}'
If rate limits are exceeded, the server returns HTTP 429 with a Retry-After header.
Summary
- Encrypt all secrets: Use a 64-character hex
ENCRYPTION_KEYto enable AES-256-GCM encryption of provider API keys in the database. - Bind carefully: Keep
HOST_BINDat127.0.0.1unless exposing to a trusted network, and always whitelistDASHBOARD_ORIGINS. - Authenticate strictly: Protect the dashboard with bcrypt-hashed passwords and the proxy endpoint with unified API keys validated via bearer tokens.
- Limit abuse: Configure
PROXY_RATE_LIMIT_RPMto enable sliding-window rate limiting and automatic cooldowns on provider errors. - Proxy TLS: Run behind Nginx or Caddy for HTTPS termination; configure
PROXY_URLfor outbound corporate proxy support. - Audit safely: Rely on built-in error redaction and hashed session IDs to prevent credential leakage in logs.
Frequently Asked Questions
How do I generate a secure ENCRYPTION_KEY for production?
Run node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" to generate a 64-character hexadecimal string. Set this as the ENCRYPTION_KEY environment variable. The server validates the length at startup in server/src/lib/crypto.ts (lines 19–26) and uses it to encrypt all stored provider credentials.
Can I run FreeLLMAPI without a reverse proxy?
While technically possible by setting HOST_BIND=0.0.0.0, this is not recommended for production. The service listens on plain HTTP, so running without a reverse proxy exposes traffic to interception and manipulation. Always terminate TLS through Nginx, Caddy, or another reverse proxy when exposing the service on any network.
How are API keys protected in the database?
Provider API keys are encrypted using AES-256-GCM before storage in the SQLite api_keys table. The encryption uses the ENCRYPTION_KEY environment variable and is handled by functions in server/src/lib/crypto.ts. The unified API key used for client authentication is also stored encrypted, and only the hashed session tokens appear in logs.
What happens when rate limits are exceeded?
When a client exceeds the PROXY_RATE_LIMIT_RPM threshold or triggers a cooldown from upstream provider errors (429/402/403), the service in server/src/services/ratelimit.ts returns HTTP 429 with a Retry-After header. The cooldown state persists in SQLite and memory to prevent immediate retry floods.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →