Environment Variables
Complete reference for the environment variables read by Flo. They are injected into the container environment; .env feeds Docker Compose, and flo config env set <id> KEY VALUE writes them on managed tenants.
ASP.NET Core configuration keys are also overridable through environment variables using the __ separator (for example Payment:Provider → PAYMENT__PROVIDER, AuditLog:HashSalt → AuditLog__HashSalt).
Production startup refuses to boot when a required variable is missing (Program.cs validates DB_CONNECTION_STRING, JWT_ISSUER, and — in Production — OIDC_SIGNING_CERT_PATH, OIDC_ENCRYPTION_CERT_PATH, FLO_APP_URL; FE_APP_URL, API_BASE_URL, FLO_CUSTOMER_NAME_SHORT and FLO_CUSTOMER_NAME_FULL are validated right after).
Required in Production
| Variable | Notes |
|---|---|
DB_CONNECTION_STRING | PostgreSQL connection string (see below) |
JWT_ISSUER | OIDC issuer, e.g. https://api.yourdomain.com |
OIDC_SIGNING_CERT_PATH | OpenIddict signing certificate (.pfx) |
OIDC_ENCRYPTION_CERT_PATH | OpenIddict encryption certificate (.pfx) |
FLO_APP_URL | Tenant apex domain, without scheme (e.g. yourdomain.com) |
FE_APP_URL | Frontend URL, e.g. https://app.yourdomain.com |
API_BASE_URL | Backend API URL used to build media/upload URLs |
FLO_CUSTOMER_NAME_SHORT | Short customer name (logo filenames, uppercase recommended) |
FLO_CUSTOMER_NAME_FULL | Full customer name (emails, UI) |
FORWARDED_NETWORKS and/or FORWARDED_PROXIES | Forwarded-header trust boundary; production refuses to start without one |
Database
| Variable | Required | Description |
|---|---|---|
DB_CONNECTION_STRING | Yes | Full Npgsql connection string |
DB_NAME | Compose | PostgreSQL database name (consumed by the postgres service) |
DB_USER | Compose | PostgreSQL username |
DB_PASSWORD | Compose | PostgreSQL password |
Connection string format:
Host=<HOST>;Port=5432;Database=<DB>;Username=<USER>;Password=<PASS>;SSL Mode=Disable
Authentication & OIDC
| Variable | Required | Description |
|---|---|---|
JWT_ISSUER | Yes | Token issuer URL |
JWT_AUDIENCE | Yes | Token audience URL |
COOKIE_DOMAIN | Prod | Auth cookie domain; a leading dot is added automatically (e.g. .yourdomain.com) |
COOKIE_NAME | No | Cookie name. Default FloAuth; shared-zone tenants get FloAuth-<slug> to avoid collisions |
OIDC_SIGNING_CERT_PATH | Prod | Path to the OpenIddict signing certificate (.pfx) |
OIDC_SIGNING_CERT_PASSWORD | Prod | Signing certificate password |
OIDC_ENCRYPTION_CERT_PATH | Prod | Path to the OpenIddict encryption certificate (.pfx) |
OIDC_ENCRYPTION_CERT_PASSWORD | Prod | Encryption certificate password |
DATAPROTECTION_CERT_PATH | No | PKCS#12 certificate used to encrypt the DataProtection key ring stored in the DB |
DATAPROTECTION_CERT_PASSWORD | No | Password for the DataProtection certificate |
DataProtection keys live in the database (DataProtectionKeys table), not in a per-container volume, so auth cookies survive restarts and ride replication to a DR standby.
Application URLs & CORS
| Variable | Required | Description |
|---|---|---|
FLO_APP_URL | Yes | Apex domain without scheme; the backend derives https://app.<domain> and https://<domain> |
FE_APP_URL | Yes | Frontend URL, used in activation/confirmation links and OAuth redirects |
API_BASE_URL | Yes | API URL used when constructing media URLs |
BLOG_URL | No | Public blog/site URL (CORS allow-list, newsletter/public-site helpers, OIDC redirect URIs) |
EXTRA_CORS_ORIGINS | No | Comma-separated extra allowed origins (e.g. Cloudflare Pages .pages.dev previews) |
Branding & Whitelabeling
| Variable | Required | Description |
|---|---|---|
FLO_CUSTOMER_NAME_SHORT | Yes | Short name; uppercased for logo filenames (LOGO_URBINOCASA.png) |
FLO_CUSTOMER_NAME_FULL | Yes | Display name used in emails and UI |
EMAIL_TEMPLATE_BRAND | No | Email template folder prefix: flo (default), pf, rs |
FLO_CUSTOMER_NAME_IT / FLO_CUSTOMER_NAME_EN and FORCE_RESPIRASTUDIO_UI are no longer read by any component; they may still be present in older tenant .env files but are inert.
Email
See Email Configuration for provider setup and the bulk/transactional distinction.
| Variable | Required | Description |
|---|---|---|
EMAIL_SENDER_EMAIL_PROVIDER | Transactional | Gmail, Outlook, Cloudflare, or a custom name when EMAIL_SENDER_SMTP_HOST is set |
EMAIL_SENDER_EMAIL_USERNAME | SMTP | SMTP username / sending account |
EMAIL_SENDER_EMAIL_PSW | SMTP | SMTP password / app password |
EMAIL_SENDER_EMAIL_ALIAS | No | Public sender address (falls back to the username) |
EMAIL_SENDER_SMTP_HOST | BYO SMTP | Custom SMTP relay host (SES, Mailgun, Postmark, …) |
EMAIL_SENDER_SMTP_PORT | BYO SMTP | Default 587 |
EMAIL_SENDER_SMTP_SSL | BYO SMTP | true to use SSL |
EMAIL_SENDER_SMTP_REQUIRE_TLS | BYO SMTP | Opt-out: STARTTLS is required unless set to false |
BULK_EMAIL_SENDER_ALIAS | No | From-address used only by the newsletter bulk queue (falls back to EMAIL_SENDER_EMAIL_ALIAS) |
CF_EMAIL_ACCOUNT_ID | Cloudflare | Cloudflare account id (transactional API dispatcher + bulk sender) |
CF_EMAIL_API_TOKEN | Cloudflare | Cloudflare API token for email sending |
CF_EMAIL_ZONE_ID | No | Cloudflare zone id for the email delivery-log analytics read |
CF_EMAIL_ANALYTICS_TOKEN | No | Analytics-scoped token; falls back to CF_EMAIL_API_TOKEN |
SES_REGION | SES bulk | AWS region (e.g. eu-west-1) |
SES_ACCESS_KEY_ID | SES bulk | AWS access key |
SES_SECRET_ACCESS_KEY | SES bulk | AWS secret key |
SES_CONFIGURATION_SET | No | SES configuration set for bounce/complaint event routing |
SWEEGO_API_KEY | Sweego bulk | Sweego API key (Api-Key header) |
EMAIL_LEGAL_BASE_URL | No | Base URL for email legal links (privacy/terms) |
EMAIL_PRIVACY_POLICY_URL | No | Full privacy-policy URL override in email footers |
EMAIL_TERMS_URL | No | Full terms URL override in email footers |
Payments
Optional, only meaningful with enable_online_payments. Secrets stay in the backend. Checkout also requires enable_bookings and enable_einvoicing.
| Variable | Description |
|---|---|
PAYMENT__PROVIDER | stripe (default) or paypal |
PAYMENT__SUCCESSURL / PAYMENT__CANCELURL | Redirects after checkout (default: frontend URL) |
PAYMENT__STRIPE__SECRETKEY | Stripe secret key (sk_live_ / sk_test_) |
PAYMENT__STRIPE__WEBHOOKSECRET | Stripe webhook signing secret (whsec_) |
PAYMENT__PAYPAL__ENVIRONMENT | sandbox or live (empty = live) |
PAYMENT__PAYPAL__CLIENTID / PAYMENT__PAYPAL__CLIENTSECRET | PayPal OAuth credentials |
PAYMENT__PAYPAL__WEBHOOKID | PayPal webhook id for signature verification |
PAYMENT__INVOICESELLER__* | Invoice seller data (NAME, VATNUMBER, FISCALCODE, REGIMEFISCALE, ADDRESS, STREETNUMBER, POSTALCODE, CITY, PROVINCE, COUNTRY, EMAIL) |
Storage (R2 / Local)
See Media Storage for the upload and optimization pipeline.
| Variable | Required | Description |
|---|---|---|
STORAGE_PROVIDER | No | local (default) or r2 |
R2_ENDPOINT | R2 | S3-compatible endpoint (https://<account-id>.r2.cloudflarestorage.com) |
R2_ACCESS_KEY | R2 | R2 access key id |
R2_SECRET_KEY | R2 | R2 secret access key |
R2_BUCKET | R2 | Bucket name |
R2_CDN_URL | R2 | Public CDN base URL (custom domain, e.g. https://cdn.yourdomain.com) |
R2_TENANT_PREFIX | R2 | Tenant key prefix (the tenant id) |
UPLOADS_VOLUME_NAME | No | Docker volume that persists /app/wwwroot/uploads (used by backup_db.sh) |
UPLOADS_PATH | No | Host path alternative for uploads backup outside Docker volumes |
Google Analytics (Web Analytics)
| Variable | Required | Description |
|---|---|---|
GOOGLE_ANALYTICS_SA_JSON | Web Analytics | Google service-account JSON as a single-line, minified string. Scoped read-only to GA4 and Search Console; never persisted to a tenant DB |
The UI shows the service account's client_email so an admin can grant it access to the GA4 property and Search Console site. See GA4 Setup.
Failover & DR
| Variable | Description |
|---|---|
FAILOVER_ENABLED | Master switch for the primary-lease subsystem. Absent/false leaves every FAILOVER_* key inert |
FAILOVER_LEASE_REQUIRED | When true, the node requires a valid MON lease to start (migrations, storage writes) |
FAILOVER_TENANT_ID | Tenant identifier validated against the lease payload |
FAILOVER_NODE_ID | Node identifier validated against the lease payload |
FAILOVER_LEASE_DIRECTORY | Absolute, tenant-only read-only mount where the lease file is published |
FAILOVER_SIGNING_KEY_ID | Key id of the MON lease signing key |
FAILOVER_SIGNING_PUBLIC_KEY_SPKI | Base64 SPKI public key used to verify the lease signature |
FAILOVER_MINIMUM_EPOCH | Replay protection: leases with a lower epoch are rejected |
FAILOVER_DB_ROLE_SEPARATED | Database posture flag: app nodes skip EF migrations (an admin-only runner applies them) |
FLO_MIGRATIONS_ONLY | Runs migrations and exits (admin-only migration runner mode) |
The control plane (flo-control, cli/deploy/control/) is a separate service and uses its own FLO_CONTROL_* variables — for example FLO_CONTROL_DR_ENABLED (gates the DR store, keys, workers and routes) and FLO_CONTROL_DR_AUTOMATION_ENABLED. Flo.BE does not read them; see cli/deploy/control/env.example.
Proxy & Client IP
| Variable | Description |
|---|---|
FORWARDED_PROXIES | Comma-separated proxy IPs whose forwarded headers are trusted |
FORWARDED_NETWORKS | Comma-separated CIDR networks whose forwarded headers are trusted (VPS default: 172.16.0.0/12,192.168.0.0/16) |
TRUST_CF_CONNECTING_IP | Whether a well-formed CF-Connecting-IP header may override the connection IP. Defaults to on outside production, off in production unless explicitly enabled |
Security & Operations
| Variable | Description |
|---|---|
MAINTENANCE_MODE | Boot-time maintenance default (true/false); can be flipped at runtime via the maintenance API |
Maintenance__ApiKey | Key for the X-Maintenance-Key header on /api/v1/maintenance/* endpoints |
AuditLog__HashSalt | Salt for audit-log hashing. Required in production |
AUDIT_HASH_SALTS | Comma-separated salt chain for salt rotation (takes precedence over the single salt) |
OTP_HMAC_PEPPER | Pepper for OTP code hashing; falls back to DB_CONNECTION_STRING when unset |
Turnstile__SecretKey | Cloudflare Turnstile secret; when set, anonymous public forms/OTP/newsletter endpoints require a CF-Turnstile-Token |
Logging__LogsPath | Overrides the log directory read by the logs viewer (default /logs) |
Runtime & Build Info
| Variable | Description |
|---|---|
ASPNETCORE_ENVIRONMENT | Development or Production |
APP_VERSION | Version shown by diagnostics; falls back to the assembly version |
GIT_COMMIT_HASH / GIT_BRANCH / BUILD_TIME | Build metadata exposed by diagnostics; injected at image build time |
NGINX_SERVER_NAME | server_name substituted into the Nginx config (nginx container) |
Strapi (Legacy CMS)
Only needed for tenants still using the legacy Strapi-backed blog/real-estate CMS.
| Variable | Description |
|---|---|
STRAPI_ENABLED | true to enable Strapi sync and configuration |
STRAPI_URL | Strapi API URL (e.g. https://strapi.yourdomain.com) |
STRAPI_API_KEY | Strapi API token |
The CLI may also write STRAPI_BLOG_URL / STRAPI_REALESTATE_URL for multi-instance tenants, but Flo.BE reads only STRAPI_URL / STRAPI_API_KEY. BLOG_EDITOR_STRAPI_API_KEY is consumed at frontend build time, not by the backend.
Example .env File
# Runtime
ASPNETCORE_ENVIRONMENT=Production
# Database
DB_NAME=flo
DB_USER=flo_user
DB_PASSWORD=secure_password
DB_CONNECTION_STRING=Host=postgres;Port=5432;Database=flo;Username=flo_user;Password=secure_password;SSL Mode=Disable
# Auth / OIDC
JWT_ISSUER=https://api.yourdomain.com
JWT_AUDIENCE=https://api.yourdomain.com
COOKIE_DOMAIN=.yourdomain.com
OIDC_SIGNING_CERT_PATH=/app/certificates/signing-cert.pfx
OIDC_SIGNING_CERT_PASSWORD=change-me
OIDC_ENCRYPTION_CERT_PATH=/app/certificates/encryption-cert.pfx
OIDC_ENCRYPTION_CERT_PASSWORD=change-me
# URLs
FLO_APP_URL=yourdomain.com
FE_APP_URL=https://app.yourdomain.com
API_BASE_URL=https://api.yourdomain.com
# Branding
FLO_CUSTOMER_NAME_SHORT=YOURCOMPANY
FLO_CUSTOMER_NAME_FULL=Your Company Name
# EMAIL_TEMPLATE_BRAND=flo
# Email (Cloudflare example)
EMAIL_SENDER_EMAIL_PROVIDER=Cloudflare
CF_EMAIL_ACCOUNT_ID=your-cf-account-id
CF_EMAIL_API_TOKEN=your-cf-api-token
EMAIL_SENDER_EMAIL_ALIAS=newsletter@yourdomain.com
# Storage (R2)
STORAGE_PROVIDER=r2
R2_ENDPOINT=https://<account-id>.r2.cloudflarestorage.com
R2_ACCESS_KEY=your-access-key
R2_SECRET_KEY=your-secret-key
R2_BUCKET=flo-media
R2_CDN_URL=https://cdn.yourdomain.com
R2_TENANT_PREFIX=mytenant
# Proxy trust (behind Traefik)
FORWARDED_NETWORKS=172.16.0.0/12,192.168.0.0/16
# Audit / maintenance
AuditLog__HashSalt=replace-with-a-random-secret
Maintenance__ApiKey=replace-with-a-random-secret
MAINTENANCE_MODE=false
NGINX_SERVER_NAME=app.yourdomain.com
# Optional integrations
# GOOGLE_ANALYTICS_SA_JSON={"type":"service_account",...}
# SWEEGO_API_KEY=...
# SES_REGION=eu-west-1
# SES_ACCESS_KEY_ID=...
# SES_SECRET_ACCESS_KEY=...
See .env.example in the repository and Single-Tenant Deployment for the full deployment flow.