How to Generate an ENCRYPTION_KEY for FreeLLMAPI
FreeLLMAPI requires a 32-byte (64-hex-character) encryption key that you can generate with a single Node.js command: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))".
FreeLLMAPI uses AES-256-GCM encryption to protect all stored provider API keys at rest. The encryption key must be provided via the ENCRYPTION_KEY environment variable and follows strict formatting requirements. This guide walks through generating a valid key and configuring it properly for both development and production environments.
Understanding the ENCRYPTION_KEY Requirements
FreeLLMAPI's encryption system, implemented in [server/src/lib/crypto.ts](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/lib/crypto.ts), enforces these key constraints:
- 32 bytes of random data (256 bits for AES-256)
- 64 hexadecimal characters when encoded as a string
- No placeholder values in production environments
The initEncryptionKey() function (lines 94-100) handles key initialization on server startup. In production, missing or invalid keys cause the server to abort immediately via missingKeyError() (lines 49-55), preventing any plaintext storage of provider credentials.
Generating a Valid ENCRYPTION_KEY
Method 1: Node.js Built-in Crypto (Recommended)
The FreeLLMAPI source code explicitly recommends this approach at [lines 30-34 of crypto.ts](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/lib/crypto.ts#L29-L36):
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
This command:
- Generates 32 cryptographically secure random bytes using
crypto.randomBytes(32) - Converts to lowercase hexadecimal with
toString('hex') - Outputs a 64-character string ready for use
Example output:
9f1e2a5c7b3d4e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f
Method 2: OpenSSL Alternative
If Node.js isn't available, use OpenSSL:
openssl rand -hex 32
Both methods produce equivalent 64-character hex strings that satisfy the parseHexKey() validation in the source code.
Configuring ENCRYPTION_KEY in Your Environment
Step 1: Create or Edit .env File
FreeLLMAPI reads environment variables from a .env file in the repository root. Add your generated key:
echo "ENCRYPTION_KEY=9f1e2a5c7b3d4e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f" >> .env
Replace the hex string with your generated value. The .env.example file in the repository documents this format with a placeholder value.
Step 2: Restart the Server
The server must reload to pick up the new environment variable:
# Native development
npm run dev
# Docker deployment
docker compose down && docker compose up -d
Step 3: Verify Key Initialization
Confirm successful key loading via the /status endpoint:
curl http://localhost:3001/status | jq '.encryptionKeyFingerprint'
Expected output:
"sha256:a1b2c3d4e5f6..."
This fingerprint is computed by encryptionKeyFingerprint() (lines 73-76 in [server/src/lib/crypto.ts](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/lib/crypto.ts)), hashing the key without exposing it. The [server/src/routes/status.ts](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/routes/status.ts) file exposes this via isEncryptionKeyInitialized().
Development vs. Production Behavior
| Environment | Key Requirement | Fallback Behavior |
|---|---|---|
| Development | Optional | Auto-generates in-memory key or uses file-based key near SQLite DB |
| Production | Mandatory | Server aborts with missingKeyError() if ENCRYPTION_KEY is unset or placeholder |
The development fallback exists for quick local testing but must not be used in production—provider keys would be unrecoverable if the in-memory key is lost.
Complete Configuration Example
# 1. Generate key
$ node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
a3f7e2d9c1b8a4f5e6d7c8b9a0f1e2d3c4b5a6f7e8d9c0b1a2f3e4d5c6b7a8f9
# 2. Persist to environment
$ echo "ENCRYPTION_KEY=a3f7e2d9c1b8a4f5e6d7c8b9a0f1e2d3c4b5a6f7e8d9c0b1a2f3e4d5c6b7a8f9" > .env
# 3. Start server and verify
$ npm run dev &
$ curl -s http://localhost:3001/status | grep encryptionKeyInitialized
"encryptionKeyInitialized": true
Key Implementation Files in FreeLLMAPI
- [
server/src/lib/crypto.ts](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/lib/crypto.ts) — Core encryption logic:initEncryptionKey(),parseHexKey(),encryptionKeyFingerprint(), and dev fallback handling - [
server/src/routes/status.ts](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/routes/status.ts) — HTTP endpoint exposing encryption status .env.example— Template documenting required environment variablesREADME.md— Quick-start documentation including install script behavior
Summary
- FreeLLMAPI ENCRYPTION_KEY must be 32 bytes (64 hex characters) for AES-256-GCM encryption
- Generate with:
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" - Configure in
.envfile asENCRYPTION_KEY=<64-hex-chars> - Verify via
/statusendpoint checkingencryptionKeyFingerprint - Production requires explicit keys—development fallbacks are insecure for live deployments
- Reference [
server/src/lib/crypto.ts](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/lib/crypto.ts) for all implementation details
Frequently Asked Questions
What happens if I don't set ENCRYPTION_KEY in production?
The server calls missingKeyError() (lines 49-55 in [crypto.ts](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/lib/crypto.ts)) and terminates immediately. This hard failure prevents the application from starting without encryption, ensuring no provider credentials are stored unencrypted.
Can I reuse the same ENCRYPTION_KEY across multiple deployments?
Yes, but this reduces security isolation. FreeLLMAPI encrypts provider keys with this key, so compromise of one deployment exposes others. Best practice: generate unique keys per environment and store them in secure secret management systems.
How do I rotate an existing ENCRYPTION_KEY?
Key rotation requires re-encrypting all stored provider keys. FreeLLMAPI does not currently expose automated key rotation—implement this by decrypting with the old key and re-encrypting with a new key via the encryption functions in [server/src/lib/crypto.ts](https://github.com/tashfeenahmed/freellmapi/blob/main/server/src/lib/crypto.ts).
Why does the status endpoint show a fingerprint instead of the full key?
The encryptionKeyFingerprint() function (lines 73-76) computes a SHA-256 hash truncated to 16 hex characters. This allows verification that a key is loaded without exposing the sensitive key material through API responses or logs.
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 →