Production Server
This guide covers running Accent CMS as a persistent production service. For static site generation, see the Static Site Build guide instead.
The --production Flag
The --production flag switches Accent CMS from development mode to production mode:
accent serve --production
Production mode changes the following behavior:
| Behavior | Dev Mode | Production Mode |
|---|---|---|
| Bind address | 127.0.0.1 only | Configured address (can bind 0.0.0.0) |
| Dev banner | Shown | Hidden |
| Hot reload | Enabled (if configured) | Disabled |
| Browser reload SSE | Active at /_dev/reload | Disabled |
| Caching | Short TTLs for fast iteration | Production TTLs for performance |
| Logging | Debug level | Info level |
Production mode requires a valid Standard or Pro license key. Without one, the server exits with an error. All features work without a license in dev mode, so you can evaluate them before purchasing.
License Key Setup
Obtain a license key from accentcms.dev/pricing. Configure it using either method:
In config.yaml
license: key: "eyJ..."
As an Environment Variable
export ACCENTCMS_LICENSE="eyJ..." accent serve --production
The environment variable takes precedence over the config file. This is the recommended approach for containerized deployments, since it keeps the license key out of version control.
Built-in TLS
Accent CMS has built-in TLS support via the --tls flag. In development mode
TLS is already the default with an automatically generated self-signed
certificate, so the flag only matters in production mode:
accent serve # Visit https://127.0.0.1:4400 accent serve --production --tls # Self-signed TLS in production mode as well
For production deployments, provide your own PEM certificate and key (requires a Standard or Pro license):
accent serve --production --tls --cert /etc/ssl/cert.pem --key /etc/ssl/key.pem
Built-in TLS is convenient for simple setups. For advanced needs (load balancing, compression, static asset caching, rate limiting), use a reverse proxy instead.
Reverse Proxy with nginx
In production, place Accent CMS behind a reverse proxy that handles TLS termination, compression, and static asset caching. Here is a minimal nginx configuration:
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:4400;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
For TLS with Let’s Encrypt, add a listen 443 ssl block and use certbot --nginx to provision certificates automatically.
Caching Static Assets
Theme assets (CSS, JS, images) do not change between deployments. Serve them directly from nginx to reduce load on Accent CMS:
location /assets/ {
alias /opt/accent/my-site/site/themes/default/assets/;
expires 30d;
add_header Cache-Control "public, immutable";
}
Reverse Proxy with Caddy
Caddy provides automatic HTTPS with no additional configuration:
example.com {
reverse_proxy localhost:4400
}
Caddy obtains and renews TLS certificates from Let’s Encrypt automatically. No extra steps are needed.
Reverse proxy with IIS
On Windows Server, IIS can forward requests to Accent CMS with two IIS extensions, URL Rewrite and Application Request Routing (ARR):
- Install both extensions.
- In IIS Manager, select the server, open Application Request Routing, and select Enable Proxy.
- Add a rewrite rule under
<system.webServer>in the site’sweb.config:
<rewrite> <rules> <rule name="Accent CMS" stopProcessing="true"> <match url="(.*)" /> <action type="Rewrite" url="http://127.0.0.1:4400/{R:1}" /> </rule> </rules> </rewrite>
Microsoft’s Reverse Proxy with URL Rewrite v2 and Application Request Routing walks through the same steps with screenshots. Caddy, above, also runs on Windows.
Behind any reverse proxy, Accent CMS sees the proxy’s address as the client’s. With protection.ip_allowlist.trust_forwarded_for: true, the IP allowlist reads the first address in the X-Forwarded-For header instead, and that address must have no port. A client can send that header itself, so turn the setting on only when your proxy replaces any X-Forwarded-For header the client sent, rather than adding to it.
Windows Firewall
Behind a reverse proxy, keep Accent CMS on 127.0.0.1 and open only the proxy’s ports. To serve Accent CMS directly, allow its port from an Administrator PowerShell:
New-NetFirewallRule -DisplayName "Accent CMS" -Direction Inbound -Protocol TCP -LocalPort 4400 -Action Allow
Running as a service
Start Accent CMS when the machine starts, and restart it after a failure.
Create a systemd service unit:
[Unit]
Description=Accent CMS
After=network.target
[Service]
Type=simple
User=accent
WorkingDirectory=/opt/accent/my-site
ExecStart=/usr/local/bin/accent serve --production
Restart=on-failure
RestartSec=5
Environment=ACCENTCMS_LICENSE=eyJ...
[Install]
WantedBy=multi-user.target
Install and enable the service:
sudo cp accent.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable --now accent
Check status and logs:
sudo systemctl status accent sudo journalctl -u accent -f
Run Accent CMS as a Windows service with accent service. Windows starts the service at boot, restarts it after a failure, and stops it cleanly at shutdown.
Put accent.exe where the service can read it, such as C:\Program Files\Accent\. A service cannot run an accent.exe inside a user profile, which is where the install script puts it.
A service does not see the variables set in your shell, and any local user can read the variables you pass to it with --env. Put the license key and the admin token in config.yaml instead:
license: key: eyJ... admin: token: a-long-random-string
From an Administrator PowerShell in the site’s directory, check the site and install the service. install refuses to register a service that would not start, and prints every problem it finds.
Set-Location C:\Sites\my-site
& 'C:\Program Files\Accent\accent.exe' validate --service-check --production
& 'C:\Program Files\Accent\accent.exe' service install -- --config config.yaml --production
Now that the service’s account exists, let only administrators and the service read config.yaml, then start the service:
icacls config.yaml /grant:r "Administrators:F" "SYSTEM:F" "NT SERVICE\accent:R"
icacls config.yaml /inheritancelevel:r
Start-Service accent
Check the status and follow the latest log:
Get-Service accent
Get-ChildItem C:\ProgramData\accent\accent\logs\*.log | Sort-Object LastWriteTime | Select-Object -Last 1 | Get-Content -Wait
To upgrade, stop the service, replace accent.exe, and start the service again: Windows does not let you replace a running program. To remove the service, run accent service uninstall. accent service lists every option.
Stopping the server
accent serve stops gracefully on SIGTERM, which systemctl stop sends, and on Ctrl+C: it stops accepting connections and lets in-flight requests finish. Over TLS it waits at most 5 seconds; over plain HTTP it waits for every request, so set TimeoutStopSec to bound a slow client. accent serve-static and accent docs serve stop the same way.
On Windows, Ctrl+Break and closing the console window also stop the server gracefully. After a console close it stops waiting after 4 seconds, because Windows ends the process 5 seconds after the window closes.
A Windows service stops the same way when an administrator runs Stop-Service accent, or when Windows shuts down. It waits at most 10 seconds, so that it stops in the time Windows allows.
Runtime Config Reload
Accent CMS supports reloading config.yaml without restarting the process. Send SIGHUP to the running service:
sudo systemctl reload accent
Add a ExecReload directive to the unit file to enable this:
ExecReload=/bin/kill -HUP $MAINPID
This allows you to change site settings, switch themes, or toggle features without downtime.
A Windows service has no SIGHUP. From an Administrator PowerShell, reload its configuration with this command, which needs no admin token:
sc.exe control accent paramchange
A reload cannot turn the server back into a development server. --production
is a command-line flag, not a config setting, so a reload re-applies its effect
to the freshly read file: dev.hot_reload, dev.browser_reload, dev.debug,
and dev.show_drafts are forced off whatever config.yaml says. To run with
development features, restart without --production.
The same goes for the path and theme flags. --theme, --theme-dir,
--content-dir, and --site-dir are re-applied to the freshly read file on
every reload, so a server started with --theme dark keeps serving dark
even if config.yaml names a different one. (--address and --port are
not: the listener is already bound, so changing them needs a restart.)
Two of the dev flags need no mistake on your part to appear: hot_reload and
browser_reload default to on, so a config.yaml that never mentions the
dev: section still asks for them at every reload. They are clamped silently.
The other two default to off, so finding debug: true or show_drafts: true
in a reloaded production config means someone wrote it — the server logs a
warning naming them, because ignoring an explicit request to expose the debug
parameter or unpublished pages is worth telling you about.
Docker Deployment
Dockerfile
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates \
&& rm -rf /var/lib/apt/lists/*
COPY accent /usr/local/bin/
COPY my-site/ /app/
WORKDIR /app
EXPOSE 4400
CMD ["accent", "serve", "--production", "--address", "0.0.0.0"]
Build and Run
docker build -t my-site . docker run -d \ --name accent \ -p 4400:4400 \ -e ACCENTCMS_LICENSE="eyJ..." \ my-site
Docker Compose
services: accent: build: . ports: - "4400:4400" environment: ACCENTCMS_LICENSE: "${ACCENTCMS_LICENSE}" volumes: - ./site/content:/app/site/content restart: unless-stopped
Mounting the content directory as a volume lets you update content without rebuilding the image.
Environment Variables
These environment variables affect production behavior:
| Variable | Description | Default |
|---|---|---|
ACCENTCMS_LICENSE | License key for production mode | None (required) |
ACCENTCMS_SMTP_PASSWORD | SMTP password referenced from config.yaml | None |
RUST_LOG | Log level (info, debug, trace) | info in production |
Any value in config.yaml may reference an environment variable as ${VAR} (or ${VAR:-default}), expanded when the config loads. A value that is exactly one placeholder takes the type the environment supplies, so numeric fields (smtp.port: "${SMTP_PORT}") and booleans can be environment-driven too. Only the placeholders themselves are rewritten: every other value reaches the server exactly as written, so a password such as 01234 keeps its leading zero. That holds under every key, including names such as og.type. The one exception is a placeholder inside an anchored value (&name reused through *name): it is expanded on the parsed tree instead, which rewrites number-like strings, and the server logs a warning naming it. ACCENTCMS_LICENSE and ACCENTCMS_SMTP_PASSWORD are the recognised secrets. To override the config file path, bind address, or port, use the --config, --address, and --port CLI flags instead.
Performance Tuning
Cache Size
The page cache stores rendered HTML in memory. Configure the maximum size:
cache: max_size_mb: 100 ttl_seconds: 7200
Each cache entry is small (the rendered HTML plus metadata). For most sites, the default 50MB is sufficient.
Cache Warming
By default, the first request to each page pays the full rendering cost (up to ~1s for pages with many syntax-highlighted code blocks). Enable cache warming to pre-render all pages at startup:
cache: warming: true
The server remains available immediately – warming runs as a background task. Once complete, every page is a cache hit from the first request. Recommended for production deployments where consistent response times matter.
HTTP Cache Headers
In production, Accent CMS automatically sets Cache-Control, ETag, and Vary headers on all responses based on content type. Browsers and reverse proxies use these headers to cache responses and avoid unnecessary re-downloads.
The defaults are optimized for typical CMS usage:
- HTML pages: 5 min browser cache, 1 hour shared cache
- CSS/JS/WASM: 1 year immutable (safe because assets change on theme reload)
- Images/media: 24 hours
- Fonts: 1 year immutable
- RSS feeds: 1 hour
Override per content type or per route in config.yaml:
http_headers: cache: defaults: pages: "public, max-age=600, s-maxage=7200" routes: - path: "/api/*" cache_control: "no-store"
Conditional requests (If-None-Match, If-Modified-Since) are handled automatically, returning 304 Not Modified when the client’s cached version is still valid.
CORS
Cross-origin resource sharing is disabled by default. Enable it when fonts, media, or API endpoints are served from a different origin or CDN subdomain:
http_headers: cors: enabled: true allowed_origins: - "https://cdn.example.com" routes: - path: "/api/*" allowed_origins: ["*"] allowed_methods: ["GET", "HEAD", "POST", "OPTIONS"]
When http_headers.cors is configured, it supersedes the legacy api.cors toggle.
Connection Limits
When running behind a reverse proxy, the proxy handles connection management. If exposing Accent CMS directly, consider OS-level tuning:
# Increase file descriptor limit for the accent user echo "accent soft nofile 65535" >> /etc/security/limits.conf
Health Checks
Accent CMS exposes a health endpoint for load balancers and monitoring:
GET /health
Response:
{ "status": "ok", "version": "0.1.0", "uptime_seconds": 3600 }
Configure your load balancer to poll this endpoint:
# nginx upstream health check (requires nginx-plus or third-party module)
upstream accent {
server 127.0.0.1:4400;
}
For Docker, add a health check to your Compose file:
services: accent: healthcheck: test: ["CMD", "curl", "-f", "http://localhost:4400/health"] interval: 30s timeout: 5s retries: 3
Next Steps
- Static Site Build – generate a static site for CDN hosting
- Configuration Reference – full
config.yamlfield reference - Deployment Overview – compare deployment strategies