prosody.cfg.lua) that uses environment variable substitution at container startup, giving you full control over domains, security, storage, and components without editing Lua directly.
Configuration file structure
The main configuration lives atapps/prosody/config/prosody.cfg.lua. It is a monolithic Lua file organised into clearly labelled sections. Environment variables are read via os.getenv() with sensible fallback defaults.
Section overview
| Section | Purpose |
|---|---|
| Plugin paths | Community and custom module directories |
modules_enabled | All globally loaded modules, grouped by function |
modules_disabled | Explicitly disabled auto-loaded modules |
| Core server settings | PID file, user/group, admin JIDs |
| Data storage | SQLite/PostgreSQL backend, per-store assignments |
| Message archiving (MAM) | Retention, compression, query limits |
| Networking | Ports, interfaces, IPv6, backend tuning |
| HTTP services | BOSH, WebSocket, file upload, CORS, security headers |
| TURN/STUN | External TURN server for audio/video calls |
| Logging | Console and file log levels, OpenMetrics |
| Security | Rate limits, registration throttling, anti-spam |
| TLS/SSL | Protocol version, ciphers, certificate paths |
| Authentication | SASL mechanisms, hashed storage, blocked usernames |
| OAuth2 | Bearer token generation for Portal integration |
| Push notifications | Cloud notify settings for mobile clients |
| VirtualHost | Primary domain with per-host modules and SSL |
| Components | MUC, file upload, proxy65, PubSub, bridge |
| Contact info | Admin contacts, server info, account cleanup |
Environment variables
All Prosody settings can be tuned via environment variables defined in.env. The config reads them with os.getenv() and falls back to defaults when unset.
Core XMPP variables
| Variable | Description | Default |
|---|---|---|
XMPP_DOMAIN | Primary XMPP domain (sets PROSODY_DOMAIN in compose) | atl.chat |
PROSODY_ADMIN_EMAIL | Admin email for contact info | admin@allthingslinux.org |
PROSODY_ADMIN_JID | Admin JID for prosodyctl and MUC ownership | admin@<domain> |
PROSODY_ALLOW_REGISTRATION | Enable in-band registration (true/false) | false |
PROSODY_LOG_LEVEL | Minimum log level (debug, info, warn, error) | info |
Port variables
| Variable | Description | Default |
|---|---|---|
PROSODY_C2S_PORT | Client-to-server (STARTTLS) | 5222 |
PROSODY_S2S_PORT | Server-to-server (federation) | 5269 |
PROSODY_HTTP_PORT | HTTP (BOSH/WebSocket) | 5280 |
PROSODY_HTTPS_PORT | HTTPS (via nginx proxy) | 5281 |
PROSODY_C2S_DIRECT_TLS_PORT | Client-to-server direct TLS | 5223 |
PROSODY_S2S_DIRECT_TLS_PORT | Server-to-server direct TLS | 5270 |
PROSODY_PROXY65_PORT | SOCKS5 file transfer proxy | 5000 |
Security variables
| Variable | Description | Default |
|---|---|---|
PROSODY_C2S_REQUIRE_ENCRYPTION | Require TLS for client connections | true |
PROSODY_S2S_REQUIRE_ENCRYPTION | Require TLS for server federation | true |
PROSODY_S2S_SECURE_AUTH | Require certificate-based s2s auth | true |
PROSODY_ALLOW_UNENCRYPTED_PLAIN_AUTH | Allow plaintext passwords without TLS | false |
PROSODY_TLS_CHANNEL_BINDING | Enable TLS channel binding for SASL | true |
PROSODY_OAUTH2_REGISTRATION_KEY | OAuth2 dynamic client registration key | (must be set) |
PROSODY_MAX_CONNECTIONS_PER_IP | Max concurrent connections per IP | 5 |
PROSODY_REGISTRATION_THROTTLE_MAX | Max registrations per throttle period | 3 |
PROSODY_REGISTRATION_THROTTLE_PERIOD | Throttle window for registration rate limiting (seconds) | 3600 |
Database variables
| Variable | Description | Default |
|---|---|---|
PROSODY_DB_PORT | Database port | 5432 |
PROSODY_DB_NAME | Database name | prosody |
PROSODY_DB_USER | Database user | prosody |
PROSODY_DB_PASSWORD | Database password | (change before production) |
MAM (message archiving) variables
| Variable | Description | Default |
|---|---|---|
PROSODY_ARCHIVE_EXPIRES_AFTER | Archive retention period | 1y |
PROSODY_ARCHIVE_POLICY | Archive all conversations by default | true |
PROSODY_ARCHIVE_COMPRESSION | Compress archived messages | true |
PROSODY_ARCHIVE_MAX_QUERY_RESULTS | Max results per MAM query | 250 |
MUC variables
| Variable | Description | Default |
|---|---|---|
PROSODY_MUC_DEFAULT_PUBLIC | New rooms are public by default | true |
PROSODY_MUC_DEFAULT_PERSISTENT | New rooms persist by default | true |
PROSODY_MUC_LOCKING | Lock rooms until configured | false |
PROSODY_MUC_LOG_BY_DEFAULT | Archive MUC messages by default | true |
PROSODY_MUC_LOG_EXPIRES_AFTER | MUC archive retention | 1y |
PROSODY_RESTRICT_ROOM_CREATION | Restrict who can create rooms | false |
TURN/STUN variables
| Variable | Description | Default |
|---|---|---|
TURN_SECRET | Shared secret with TURN server | (change before production) |
TURN_EXTERNAL_HOST | TURN server hostname | turn.atl.network |
TURN_PORT | TURN/STUN UDP port | 3478 |
TURNS_PORT | TURN over TLS port | 5349 |
Rate limiting variables
| Variable | Description | Default |
|---|---|---|
PROSODY_C2S_RATE | Client connection rate limit | 10kb/s |
PROSODY_C2S_BURST | Client burst allowance | 25kb |
PROSODY_C2S_STANZA_SIZE | Max client stanza size (bytes) | 262144 |
PROSODY_S2S_RATE | Server federation rate limit | 30kb/s |
PROSODY_S2S_BURST | Server burst allowance | 100kb |
PROSODY_S2S_STANZA_SIZE | Max server stanza size (bytes) | 524288 |
VirtualHost configuration
Prosody uses a singleVirtualHost block for the primary domain. The domain is set dynamically from the XMPP_DOMAIN environment variable (mapped to PROSODY_DOMAIN in compose).
http_hostmaps incoming HTTP requests to this VirtualHost, which is important when Prosody sits behind a reverse proxyhttp_admin_apiis loaded only on the VirtualHost (not globally) so the REST API is scoped to this domaindefault_bookmarksauto-joins new users to the#generalMUC room- SSL certificates follow the Let’s Encrypt directory layout (
certs/live/<domain>/) - BOSH/WebSocket URLs are dynamically derived from
PROSODY_HTTP_EXTERNAL_URLwhen set
Component blocks
Components extend Prosody with additional services. Each component gets its own subdomain and SSL certificate.MUC (multi-user chat)
general room on startup with the admin JID as owner. Room defaults (public, persistent, logged) are controlled via PROSODY_MUC_* environment variables.
MUC rate limiting is configured globally:
| Setting | Value | Description |
|---|---|---|
muc_event_rate | 0.5 | Max events/second (one every 2 seconds) |
muc_burst_factor | 6 | Allow 6× burst for 6 seconds |
muc_max_nick_length | 23 | Max nickname length |
muc_max_char_count | 5664 | Max bytes per message |
muc_max_line_count | 23 | Max lines per message |
HTTP file upload
| Setting | Value |
|---|---|
| Max file size | 100 MB |
| Daily quota per user | 1 GB |
| Global quota | 10 GB |
| File expiration | 30 days |
SOCKS5 proxy (XEP-0065)
PROSODY_PROXY65_PORT).
PubSub with RSS feeds
Bridge component (XEP-0114)
component_interfaces = { "*" }) so the bridge container can reach it across the Docker network.
Warning: Change BRIDGE_XMPP_COMPONENT_SECRET from the default before production deployment.
Storage configuration
Prosody uses SQLite by default for development and can be switched to PostgreSQL for production viaPROSODY_STORAGE=sql.
| Data type | Backend | Notes |
|---|---|---|
| accounts, roster, vcard, private, blocklist | sql | User data |
| archive, muc_log, offline | sql | Message archives |
| pubsub_nodes, pubsub_data, pep | sql | PubSub and PEP |
| http_file_share | sql | File upload metadata |
| caps, carbons | memory | Ephemeral (not persisted) |
data/xmpp/data/ on the host.
TLS and security
Global TLS settings
Encryption enforcement
| Setting | Default | Recommendation |
|---|---|---|
c2s_require_encryption | true | Always true in production |
s2s_require_encryption | true | Always true in production |
s2s_secure_auth | true | true for certificate-based federation auth |
allow_unencrypted_plain_auth | false | Never true in production |
Authentication
Prosody uses hashed password storage with SCRAM-SHA-256 as the primary SASL mechanism:mod_http_admin_api.
Rate limiting
Three rate limit tiers protect against abuse:| Tier | Rate | Burst | Max stanza |
|---|---|---|---|
| Client (c2s) | 10 KB/s | 25 KB | 256 KB |
| Server (s2s) | 30 KB/s | 100 KB | 512 KB |
| HTTP upload | 2 MB/s | 10 MB | — |
max_connections_per_ip: 5 (increase if bridge shares an IP)anti_spam_services: subscribes toxmppbl.orgreal-time block listsblock_registrations_users: blocks common abusive usernames (admin, root, postmaster, etc.)block_registrations_require: enforces^[a-zA-Z0-9_.-]+$pattern for usernames
HTTP security headers
Prosody serves HTTP responses with hardened headers:| Header | Value |
|---|---|
Strict-Transport-Security | max-age=31536000; includeSubDomains; preload |
X-Frame-Options | DENY |
X-Content-Type-Options | nosniff |
X-XSS-Protection | 1; mode=block |
Referrer-Policy | strict-origin-when-cross-origin |
Content-Security-Policy | Allows self and XMPP endpoints (connect-src for BOSH/WebSocket) |
Audit findings and recommendations
The following findings are from a configuration audit and remain relevant to the current setup.Resolved issues
These critical issues from the original audit have been fixed in the current configuration:- TLS enforcement defaults:
.env.examplenow ships withPROSODY_C2S_REQUIRE_ENCRYPTION=true,PROSODY_S2S_REQUIRE_ENCRYPTION=true,PROSODY_S2S_SECURE_AUTH=true, andPROSODY_ALLOW_UNENCRYPTED_PLAIN_AUTH=false. - Global SSL block: The
sslblock is now active (not commented out) with TLS 1.2+ and modern ciphers. - Legacy auth removed:
legacyauthis no longer inmodules_enabled— only SASL (SCRAM-SHA-256/SHA-1) is used. - HTTP status endpoint:
http_status_allow_cidris restricted to172.16.0.0/12and127.0.0.0/8(Docker networks and localhost), not world-open.
Active considerations
- Archive retention:
PROSODY_ARCHIVE_EXPIRES_AFTERdefaults to1y. Monitor SQLite database size over time, especially with many active users. - Connections per IP:
PROSODY_MAX_CONNECTIONS_PER_IPdefaults to5. In Docker environments where the bridge and other services connect from the same network IP, you may need to increase this value. mod_registerfor password changes: Registration is disabled (Portal provisions users), butmod_registeris also needed for password changes by existing users. Consider enabling it withallow_registration = falseif users need self-service password changes.- Namespace exclusions:
dont_archive_namespacesexcludes typing indicators (chatstates) and Jingle call signaling from archives, reducing storage usage.
Positive findings
The configuration exceeds typical Prosody deployments in several areas:- Comprehensive module selection covering core protocol, modern messaging (MAM, carbons, smacks), mobile optimization (CSI, cloud_notify), and spam prevention (anti_spam, blocklist, report_forward)
- Push notification privacy:
push_notification_with_bodyandpush_notification_with_senderare bothfalseby default - Anti-spam with xmppbl.org RTBL subscription
- Thorough HTTP security headers including HSTS, CSP, and X-Frame-Options
- Proper TURN/STUN external configuration for audio/video calls
- Certificate handling with Let’s Encrypt layout, legacy fallback, and self-signed generation
Customising the configuration
To modify Prosody settings:- Set environment variables in
.env(preferred for most settings) - Edit
apps/prosody/config/prosody.cfg.luadirectly for structural changes - Reload the configuration without restarting:
- Verify the configuration is valid:
Related pages
- XMPP Overview — architecture and technology stack
- XMPP DNS — SRV records and DNS setup
- XMPP Modules — module reference
- XMPP Operations — operational commands and management
- Environment Variables — complete variable reference
- SSL/TLS — certificate management across all services