ngx_l402 — L402 Nginx Module
An L402 authentication module for Nginx that enables Lightning Network-based monetization for your REST APIs (HTTP/1 and HTTP/2).
It supports the following Lightning backends:
| Backend | Description |
|---|---|
| LND | Lightning Network Daemon (direct gRPC) |
| LNC | Lightning Node Connect (remote LND via mailbox) |
| CLN | Core Lightning |
| Eclair | Eclair node |
| LNURL | Lightning Network URL |
| NWC | Nostr Wallet Connect |
| BOLT12 | Reusable Lightning Offers |
The module can be configured to charge per unique API call, enabling per-endpoint monetization based on request paths.
How It Works
graph TD;
A[Request Received] --> B{Endpoint L402 Enabled?}
B -->|No| C[Return 200 OK]
B -->|Yes| D{"Any auth header present? (L402 or X-Cashu)"}
D -->|No| F[Generate L402 Header macaroon & invoice]
D -->|Yes| K["Parse L402 macaroon/preimage or X-Cashu (if present)"]
F --> G{Header Generation Success?}
G -->|No| I[Return 500 Internal Server Error]
G -->|Yes| H[Add WWW-Authenticate Header]
H --> J[Return 402 Payment Required]
K --> L{Parse Success?}
L -->|No| M[Return 500 Internal Server Error]
L -->|Yes| N["Verify macaroon/preimage OR Cashu proofs (whitelist; P2PK lock if enabled; double-spend check; amount >= price)"]
N --> O{Verification Success?}
O -->|No| Q[Return 401 Unauthorized]
O -->|Yes| P[Return 200 OK]
Quick Start
Note: This module requires NGINX version 1.28.0 or later.
The fastest way to get started is with Docker:
docker run -d \
--name l402-nginx \
-p 8000:8000 \
-e LN_CLIENT_TYPE=LNURL \
-e LNURL_ADDRESS=username@your-lnurl-server.com \
-e ROOT_KEY=your-32-byte-hex-key \
ghcr.io/dhananjaypurohit/ngx_l402:latest
Then test it:
# Should return 200 OK
curl http://localhost:8000/
# Should return 402 Payment Required with L402 header
curl -i http://localhost:8000/protected
See the Installation section for full setup options.
Manual Installation
Note: This module requires NGINX version 1.28.0 or later. Earlier versions will cause module version mismatch errors.
Steps
1. Download the Module
Download libngx_l402_lib.so from the latest release and copy it to your Nginx modules directory:
sudo cp libngx_l402_lib.so /etc/nginx/modules/
2. Load the Module in nginx.conf
load_module /etc/nginx/modules/libngx_l402_lib.so;
3. Enable L402 for Specific Locations
location /protected {
root /usr/share/nginx/html;
index index.html index.htm;
# L402 module directives:
l402 on;
l402_amount_msat_default 10000;
# Note: Dynamic pricing is handled via Redis using the request path as key
# Example: SET /protected 15000 (sets price to 15000 msats for /protected endpoint)
l402_macaroon_timeout 3600; # Macaroon validity in seconds, set to 0 to disable timeout
# Optional: per-location LNURL address for multi-tenant setups
# l402_lnurl_addr "tenant@your-lnurl-server.com";
}
4. Set Environment Variables
Set the following in nginx.service (typically /lib/systemd/system/nginx.service).
See Environment Variables for the complete reference.
5. Set Up SQLite Database Directory (if using Cashu)
# One-time setup — persists across restarts
sudo mkdir -p /var/lib/nginx
sudo chown nginx:nginx /var/lib/nginx
sudo chmod 755 /var/lib/nginx
The
cdk-sqlitecrate automatically creates the database file and tables on first run. Database location:/var/lib/nginx/cashu_tokens.db
Note: Both
restartandreloadare needed to ensure the Cashu redemption task starts properly.
6. Restart Nginx
sudo systemctl restart nginx
sudo systemctl reload nginx
Note: Both
restartandreloadare needed to ensure the Cashu redemption task starts properly.
Docker Installation
The easiest way to deploy the L402 Nginx module is with our official Docker images.
docker pull ghcr.io/dhananjaypurohit/ngx_l402:latest
Quick Start Examples
1. LNURL Backend (Simplest Setup)
docker run -d \
--name l402-nginx \
-p 8000:8000 \
-e LN_CLIENT_TYPE=LNURL \
-e LNURL_ADDRESS=username@your-lnurl-server.com \
-e ROOT_KEY=your-32-byte-hex-key \
ghcr.io/dhananjaypurohit/ngx_l402:latest
2. LND Backend with Cashu Support
mkdir -p ~/l402-data
cp ~/.lnd/data/chain/bitcoin/mainnet/admin.macaroon ~/l402-data/
cp ~/.lnd/tls.cert ~/l402-data/
docker run -d \
--name l402-nginx \
-p 8000:8000 \
-e LN_CLIENT_TYPE=LND \
-e LND_ADDRESS=your-lnd-ip:10009 \
-e MACAROON_FILE_PATH=/app/data/admin.macaroon \
-e CERT_FILE_PATH=/app/data/tls.cert \
-e CASHU_ECASH_SUPPORT=true \
-e CASHU_WALLET_SECRET=your-32-byte-hex-secret \
-e CASHU_DB_PATH=/app/data/cashu_tokens.db \
-e CASHU_WHITELISTED_MINTS=https://mint1.example.com,https://mint2.example.com \
-e CASHU_REDEEM_ON_LIGHTNING=true \
-e REDIS_URL=redis://redis:6379 \
-v ~/l402-data:/app/data \
ghcr.io/dhananjaypurohit/ngx_l402:latest
3. LND via Lightning Node Connect (LNC)
# Generate a pairing phrase from Lightning Terminal first:
# litcli sessions add --label="nginx-l402" --type=admin
docker run -d \
--name l402-nginx \
-p 8000:8000 \
-e LN_CLIENT_TYPE=LND \
-e LNC_PAIRING_PHRASE="word1 word2 word3 word4 word5 word6 word7 word8 word9 word10" \
-e LNC_MAILBOX_SERVER=mailbox.terminal.lightning.today:443 \
-e ROOT_KEY=your-32-byte-hex-key \
ghcr.io/dhananjaypurohit/ngx_l402:latest
4. CLN Backend (Core Lightning)
docker run -d \
--name l402-nginx \
-p 8000:8000 \
-e LN_CLIENT_TYPE=CLN \
-e CLN_LIGHTNING_RPC_FILE_PATH=/app/data/lightning-rpc \
-e ROOT_KEY=your-32-byte-hex-key \
-e CASHU_ECASH_SUPPORT=true \
-e CASHU_WALLET_SECRET=your-32-byte-hex-secret \
-e CASHU_DB_PATH=/app/data/cashu_tokens.db \
-v ~/.lightning/bitcoin/lightning-rpc:/app/data/lightning-rpc:ro \
ghcr.io/dhananjaypurohit/ngx_l402:latest
5. NWC Backend (Nostr Wallet Connect)
docker run -d \
--name l402-nginx \
-p 8000:8000 \
-e LN_CLIENT_TYPE=NWC \
-e NWC_URI=nostr+walletconnect://your-pubkey?relay=wss://relay.damus.io&secret=your-secret \
-e ROOT_KEY=your-32-byte-hex-key \
ghcr.io/dhananjaypurohit/ngx_l402:latest
6. High-Performance P2PK Mode (Recommended for Production)
docker run -d \
--name l402-nginx \
-p 8000:8000 \
-e LN_CLIENT_TYPE=LND \
-e LND_ADDRESS=your-lnd-ip:10009 \
-e MACAROON_FILE_PATH=/app/data/admin.macaroon \
-e CERT_FILE_PATH=/app/data/tls.cert \
-e CASHU_ECASH_SUPPORT=true \
-e CASHU_P2PK_MODE=true \
-e CASHU_P2PK_PRIVATE_KEY=your-32-byte-hex-private-key \
-e CASHU_WALLET_SECRET=your-32-byte-hex-secret \
-e CASHU_DB_PATH=/app/data/cashu_tokens.db \
-e CASHU_WHITELISTED_MINTS=https://mint1.example.com \
-e CASHU_REDEEM_ON_LIGHTNING=true \
-e REDIS_URL=redis://redis:6379 \
-v ~/l402-data:/app/data \
ghcr.io/dhananjaypurohit/ngx_l402:latest
7. BOLT12 Backend (Reusable Offers)
docker run -d \
--name l402-nginx \
-p 8000:8000 \
-e LN_CLIENT_TYPE=BOLT12 \
-e BOLT12_OFFER=lno1... \
-e CLN_LIGHTNING_RPC_FILE_PATH=/app/data/lightning-rpc \
-e ROOT_KEY=your-32-byte-hex-key \
-v ~/.lightning/bitcoin/lightning-rpc:/app/data/lightning-rpc:ro \
ghcr.io/dhananjaypurohit/ngx_l402:latest
8. Eclair Backend
docker run -d \
--name l402-nginx \
-p 8000:8000 \
-e LN_CLIENT_TYPE=ECLAIR \
-e ECLAIR_ADDRESS=http://your-eclair-node:8282 \
-e ECLAIR_PASSWORD=your-eclair-password \
-e ROOT_KEY=your-32-byte-hex-key \
ghcr.io/dhananjaypurohit/ngx_l402:latest
Generating Required Secrets
# ROOT_KEY (required for all setups)
openssl rand -hex 32
# CASHU_WALLET_SECRET (for Cashu support)
openssl rand -hex 32
# CASHU_P2PK_PRIVATE_KEY (for P2PK mode)
openssl rand -hex 32
Testing Your Setup
# Test free endpoint
curl http://localhost:8000/
# Test protected endpoint (should return 402 with L402 header)
curl -i http://localhost:8000/protected
# Check container logs
docker logs l402-nginx -f
# Stop the container
docker stop l402-nginx
Specific Versions
docker pull ghcr.io/dhananjaypurohit/ngx_l402:v1.2.3
Environment Variables
All configuration is done via environment variables set in nginx.service (typically at /lib/systemd/system/nginx.service).
[Service]
...
Environment=VAR_NAME=value
Lightning Client Type
| Variable | Required | Description |
|---|---|---|
LN_CLIENT_TYPE | ✅ | One of: LND, CLN, LNURL, NWC, BOLT12, ECLAIR |
LND (Direct gRPC)
Environment=LN_CLIENT_TYPE=LND
Environment=LND_ADDRESS=your-lnd-ip.com
Environment=MACAROON_FILE_PATH=/path/to/macaroon
Environment=CERT_FILE_PATH=/path/to/cert
Environment=ROOT_KEY=your-root-key
LND via Lightning Node Connect (LNC)
Environment=LN_CLIENT_TYPE=LND
Environment=LNC_PAIRING_PHRASE=<10-word-mnemonic-from-litd>
Environment=LNC_MAILBOX_SERVER=mailbox.terminal.lightning.today:443
Environment=ROOT_KEY=your-root-key
CLN (Core Lightning)
Environment=LN_CLIENT_TYPE=CLN
Environment=CLN_LIGHTNING_RPC_FILE_PATH=/path/to/lightning-rpc
Environment=ROOT_KEY=your-root-key
LNURL
Environment=LN_CLIENT_TYPE=LNURL
Environment=LNURL_ADDRESS=username@your-lnurl-server.com
Environment=ROOT_KEY=your-root-key
NWC (Nostr Wallet Connect)
Environment=LN_CLIENT_TYPE=NWC
Environment=NWC_URI=nostr+walletconnect://<pubkey>?relay=<relay_url>&secret=<secret>
Environment=ROOT_KEY=your-root-key
BOLT12 (Reusable Offers)
Environment=LN_CLIENT_TYPE=BOLT12
Environment=BOLT12_OFFER=lno1...
Environment=CLN_LIGHTNING_RPC_FILE_PATH=/path/to/lightning-rpc
Environment=ROOT_KEY=your-root-key
Eclair
Environment=LN_CLIENT_TYPE=ECLAIR
Environment=ECLAIR_ADDRESS=http://127.0.0.1:8282
Environment=ECLAIR_PASSWORD=eclairpass
Environment=ROOT_KEY=your-root-key
Redis (Dynamic Pricing & Replay Protection)
Environment=REDIS_URL=redis://127.0.0.1:6379
# TTL for replay attack prevention (default: 86400 = 24 hours)
Environment=L402_PREIMAGE_TTL_SECONDS=86400
Environment=L402_CASHU_TOKEN_TTL_SECONDS=86400
Cashu eCash
Environment=CASHU_ECASH_SUPPORT=true
Environment=CASHU_DB_PATH=/var/lib/nginx/cashu_tokens.db
Environment=CASHU_WALLET_SECRET=<your-secret-random-string>
# Optional: Whitelist specific mints (comma-separated)
# In standard mode: if not set, all mints are accepted
# In P2PK mode: REQUIRED for security and NUT-24 payment request
Environment=CASHU_WHITELISTED_MINTS=https://mint1.example.com,https://mint2.example.com
# Optional: Auto-redeem Cashu tokens to Lightning
Environment=CASHU_REDEEM_ON_LIGHTNING=true
Environment=CASHU_REDEMPTION_INTERVAL_SECS=3600 # default: 1 hour
⚠️ Security:
CASHU_WALLET_SECRETis used to generate the wallet seed. Anyone with this secret can steal your tokens!
- Generate with:
openssl rand -hex 32- Never commit to Git
- Use a different value per deployment/environment
- Keep it in a secure environment variable or secrets manager
Redemption Fee Handling
# Minimum balance to attempt melting (default: 10 sats)
Environment=CASHU_MELT_MIN_BALANCE_SATS=10
# Percentage to reserve for fees (default: 1%)
Environment=CASHU_MELT_FEE_RESERVE_PERCENT=1
# Minimum fee reserve when percentage is small (default: 4 sats)
Environment=CASHU_MELT_MIN_FEE_RESERVE_SATS=4
# Maximum proofs per melt operation (default: 0 = unlimited)
# Logic: if proof_count > limit, select first N proofs, rest remain for next cycle
# Use case: prevent hitting mint proof limits (e.g. mint.coinos.io has 1000 proof limit)
Environment=CASHU_MAX_PROOFS_PER_MELT=1000
P2PK Mode (High Performance)
Environment=CASHU_P2PK_MODE=true
Environment=CASHU_P2PK_PRIVATE_KEY=<your-private-key-hex>
# Public key is derived automatically from the private key
# CASHU_WHITELISTED_MINTS is REQUIRED in P2PK mode
⚠️ Security:
CASHU_P2PK_PRIVATE_KEYis equally critical. Anyone with this key can spend tokens locked to your public key!
- Generate with:
openssl rand -hex 32- Never commit to Git or share publicly
- Keep it secure alongside
CASHU_WALLET_SECRET
See Cashu eCash for a full explanation of Standard vs P2PK mode and redemption fee examples.
Logging
Environment=RUST_LOG=info
# For module-specific debug logs:
Environment=RUST_LOG=ngx_l402_lib=debug,info
Redis & Dynamic Configuration
The module supports real-time configuration updates via Redis without requiring an Nginx reload.
Setup
Environment=REDIS_URL=redis://127.0.0.1:6379
Dynamic Pricing
Set the price for a specific path in Redis. Changes are picked up immediately by the next request.
# Set price to 1000 msats for /api/resource
SET /api/resource 1000
# Set price to 5000 msats for /api/premium
SET /api/premium 5000
Note: If no Redis key exists for a path, the module falls back to
l402_amount_msat_defaultinnginx.conf.
Dynamic LNURL (Per-Tenant Routing)
Override the LNURL address for a specific request path. This takes precedence over l402_lnurl_addr in nginx.conf.
Key format: lnurl:<request_path>
# Route /api/tenant1 payments to alice
SET lnurl:/api/tenant1 alice@getalby.com
# Route /api/tenant2 payments to bob
SET lnurl:/api/tenant2 bob@getalby.com
Replay Attack Prevention
Redis is used to enforce single-use of L402 preimages and Cashu tokens, preventing replay attacks across distributed deployments.
Environment=REDIS_URL=redis://127.0.0.1:6379
Environment=L402_PREIMAGE_TTL_SECONDS=86400 # Default: 24 hours
Environment=L402_CASHU_TOKEN_TTL_SECONDS=86400 # Default: 24 hours
How it works: After successful verification, SHA256 hashes of preimages/tokens are stored in Redis with a TTL. Subsequent use of the same credential is rejected with 401. Protection persists across Nginx restarts and works with multiple Nginx instances.
Multi-Tenant Configuration
The module supports multi-tenant mode, allowing different API routes to use different Lightning/LNURL backends. This is useful for platforms hosting multiple merchants or services, where each tenant receives payments to their own wallet.
Current Support: Multi-tenant is currently supported for Cashu eCash payments only when using
LN_CLIENT_TYPE=LNURL.
How It Works
- Per-location LNURL addresses: Use the
l402_lnurl_addrdirective to specify a different LNURL address per Nginx location block. - Proof tracking: When a Cashu token is received, the proofs are mapped to the tenant’s LNURL address in Redis.
- Grouped redemption: The automatic redemption task groups proofs by tenant and redeems each group to the correct LNURL address.
Nginx Configuration
# Tenant 1 — payments go to alice@getalby.com
location /api/tenant1 {
l402 on;
l402_amount_msat_default 10000;
l402_macaroon_timeout 0;
l402_lnurl_addr "alice@getalby.com";
}
# Tenant 2 — payments go to bob@getalby.com
location /api/tenant2 {
l402 on;
l402_amount_msat_default 15000;
l402_macaroon_timeout 0;
l402_lnurl_addr "bob@getalby.com";
}
# Tenant 3 — self-hosted LNURL server
location /api/tenant3 {
l402 on;
l402_amount_msat_default 5000;
l402_macaroon_timeout 0;
l402_lnurl_addr "user@your-lnurl-server.com";
}
Required Environment Variables
# Use LNURL client type
Environment=LN_CLIENT_TYPE=LNURL
# Default LNURL address (fallback when l402_lnurl_addr is not set)
Environment=LNURL_ADDRESS=default@your-domain.com
# Redis is required for proof-to-tenant mapping
Environment=REDIS_URL=redis://127.0.0.1:6379
# Enable Cashu eCash support
Environment=CASHU_ECASH_SUPPORT=true
Environment=CASHU_WALLET_SECRET=<your-secret>
Environment=CASHU_WHITELISTED_MINTS=https://mint.example.com
# Enable automatic redemption to Lightning
Environment=CASHU_REDEEM_ON_LIGHTNING=true
Environment=CASHU_REDEMPTION_INTERVAL_SECS=60
Dynamic LNURL Override via Redis
You can also override the LNURL address per path dynamically without reloading Nginx:
SET lnurl:/api/tenant1 alice@getalby.com
SET lnurl:/api/tenant2 bob@getalby.com
See Redis & Dynamic Config for more details.
Lightning Network Payments
ngx_l402 implements the L402 protocol, enabling API monetization via Lightning Network payments. When a client hits a protected endpoint without a valid token, the module responds with 402 Payment Required and a Lightning invoice. The client pays the invoice, receives a preimage, and presents it alongside the macaroon to gain access.
Supported Backends
Configure the backend via the LN_CLIENT_TYPE environment variable:
LN_CLIENT_TYPE | Description |
|---|---|
LND | Lightning Network Daemon — direct gRPC connection |
LNC | Lightning Node Connect — remote LND via mailbox (no open port needed) |
CLN | Core Lightning |
ECLAIR | Eclair node |
LNURL | Lightning Network URL — delegate invoice generation to an LNURL server |
NWC | Nostr Wallet Connect |
BOLT12 | Reusable Lightning Offers (BOLT12) |
See Environment Variables for the full list of per-backend settings.
Payment Flow
- Client requests a protected endpoint (no auth header).
- Module generates a macaroon and requests an invoice from the configured Lightning backend.
- Module responds
402 Payment Requiredwith:WWW-Authenticate: L402 macaroon="<macaroon>", invoice="<bolt11>" - Client pays the invoice and obtains the preimage.
- Client retries with:
Authorization: L402 <macaroon>:<preimage> - Module verifies the macaroon + preimage and returns
200 OK.
Authorization Header Format
Authorization: L402 <macaroon>:<preimage>
The macaroon and preimage are separated by a colon (
:). The preimage must be the 32-byte (256-bit) hex-encoded payment preimage corresponding to the invoice’spayment_hash.
Wallet Compatibility
Warning
Some wallets (e.g. Wallet of Satoshi) return 48-byte non-standard preimages, which are not compatible with this module. Use a wallet that returns a standard 32-byte preimage.
Also Supported: Cashu eCash
In addition to Lightning, the module accepts Cashu eCash tokens via the X-Cashu header as an alternative payment method. See Cashu eCash Support for details.
Cashu eCash
The module supports Cashu eCash tokens as an alternative payment method to Lightning invoices.
Standard Mode vs P2PK Mode
| Standard Mode | P2PK Mode | |
|---|---|---|
| How it works | Calls wallet.receive() → contacts mint to swap tokens | Verifies token locked to proxy’s public key locally |
| Speed | Slower (blocks on mint API call per request) | Fast (milliseconds — no mint call!) |
| Best for | Low-traffic or simple setups | High-traffic production deployments |
| Extra requirement | None | CASHU_WHITELISTED_MINTS is required |
Standard Mode Setup
Environment=CASHU_ECASH_SUPPORT=true
Environment=CASHU_DB_PATH=/var/lib/nginx/cashu_tokens.db
Environment=CASHU_WALLET_SECRET=<your-secret-random-string>
# Optional: Whitelist specific mints (comma-separated)
Environment=CASHU_WHITELISTED_MINTS=https://mint1.example.com,https://mint2.example.com
# Optional: Auto-redeem to Lightning
Environment=CASHU_REDEEM_ON_LIGHTNING=true
Environment=CASHU_REDEMPTION_INTERVAL_SECS=3600
⚠️ Security:
CASHU_WALLET_SECRETis used to generate the wallet seed. Anyone with this secret can steal your tokens! Generate withopenssl rand -hex 32and never commit it to Git.
P2PK Mode Setup (High Performance)
Environment=CASHU_P2PK_MODE=true
Environment=CASHU_P2PK_PRIVATE_KEY=<your-private-key-hex>
# CASHU_WHITELISTED_MINTS is REQUIRED in P2PK mode
Environment=CASHU_WHITELISTED_MINTS=https://mint1.example.com
⚠️ Security:
CASHU_P2PK_PRIVATE_KEYis equally critical. Anyone with this key can spend tokens locked to your public key. Generate withopenssl rand -hex 32.
How P2PK mode works per request:
- Proxy derives a public key from
CASHU_P2PK_PRIVATE_KEYand sends it to clients via theX-Cashuheader (NUT-24) - Client creates P2PK-locked tokens to that public key
- Proxy verifies tokens are locked to its public key (NUT-11) — local check, no network call
- Proxy unlocks proofs with private key (local cryptographic operation)
- Unlocked proofs are stored directly in CDK database via
wallet.receive_proofs() - Background redemption task finds proofs via
wallet.get_unspent_proofs()and redeems to Lightning viawallet.melt()
Redemption Fee Configuration
# Minimum balance to attempt melting (default: 10 sats)
Environment=CASHU_MELT_MIN_BALANCE_SATS=10
# Percentage to reserve for fees (default: 1%)
Environment=CASHU_MELT_FEE_RESERVE_PERCENT=1
# Minimum fee reserve when percentage is small (default: 4 sats)
Environment=CASHU_MELT_MIN_FEE_RESERVE_SATS=4
# Maximum proofs per melt operation (default: 0 = unlimited)
# Use this if your mint has a per-melt proof limit (e.g. mint.coinos.io = 1000)
Environment=CASHU_MAX_PROOFS_PER_MELT=1000
Fee calculation: fee_reserve = max(total_amount × percent/100, min_fee_sats)
Example 1 — Large balance (500 sats) with 1% fee reserve:
- Percentage fee:
500 × 1% = 5 sats - Minimum fee:
4 sats - Used reserve:
max(5, 4) = 5 sats - Redeemable:
500 - 5 = 495 sats
Example 2 — Small balance (50 sats) with 1% fee reserve:
- Percentage fee:
50 × 1% = 0.5 sats - Minimum fee:
4 sats - Used reserve:
max(0.5, 4) = 4 sats← Minimum kicks in! - Redeemable:
50 - 4 = 46 sats
Example 3 — Proof count limiting when exceeding mint limit (CASHU_MAX_PROOFS_PER_MELT=1000):
- Scenario: 1282 proofs worth 13,588 sats total
- Check:
1282 proofs > 1000 limit→ Limiting triggered - Action: Select first 1000 proofs worth ~10,600 sats
- Invoice: Generate invoice for 10,600 sats
- Remaining: 282 proofs (~2,988 sats) stay for next cycle
- Next cycle:
282 proofs < 1000 limit→ all remaining proofs melted
Actual melt quote fees are verified against the reserve; warnings appear if the reserve was insufficient.
Note on
CASHU_WHITELISTED_MINTS: If not configured, all mints are accepted in standard mode. In P2PK mode, whitelisted mints are REQUIRED for security and the payment request (NUT-24).
SQLite Database Setup
# One-time setup — persists across restarts
sudo mkdir -p /var/lib/nginx
sudo chown nginx:nginx /var/lib/nginx
sudo chmod 755 /var/lib/nginx
The cdk-sqlite crate automatically creates the database file and tables. Database location: /var/lib/nginx/cashu_tokens.db
Building from Source
Prerequisites
Install required system dependencies:
sudo apt-get install -y \
build-essential \
clang \
libclang-dev \
libc6-dev \
zlib1g-dev \
pkg-config \
libssl-dev \
protobuf-compiler \
nginx
Install Rust and Cargo:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Build Steps
- Clone the repository:
git clone https://github.com/DhananjayPurohit/ngx_l402.git
cd ngx_l402
- Build the module:
cargo build --release --features export-modules
The compiled module will be at target/release/libngx_l402_lib.so.
- Copy to your Nginx modules directory:
sudo cp target/release/libngx_l402_lib.so /etc/nginx/modules/
- Follow the remaining manual installation steps.
Logging
View Logs
systemd / Manual Install
# Module initialization and system logs
sudo journalctl -u nginx
# Nginx error logs (real-time)
sudo tail -f /var/log/nginx/error.log
# Cashu redemption logs
sudo tail -f /var/log/nginx/cashu_redemption.log
Docker
docker logs l402-nginx -f
Log Levels
Control verbosity via the RUST_LOG environment variable:
# Standard info logs (recommended for production)
Environment=RUST_LOG=info
# Detailed debug logs for all modules
Environment=RUST_LOG=debug
# Module-specific debug logs only (reduces noise)
Environment=RUST_LOG=ngx_l402_lib=debug,info