Operation
Startup options
| Option | Environment variable | Default | |
|---|---|---|---|
-addr |
IC_ADDR |
:8080 |
Address and port |
-data |
IC_DATA |
./data |
Data folder |
-cache-mb |
IC_CACHE_MB |
128 |
Page cache in MB, 0 = off |
-secure-cookies |
IC_SECURE_COOKIES=1 |
off | Session cookie only over HTTPS – be sure to enable it behind HTTPS |
-rate-limit |
IC_RATE_LIMIT |
120 |
API requests per minute and IP without signing in (ten times as many with token/session), 0 = off – see API docs |
-headless |
IC_HEADLESS=1 |
off | Enforce running without the admin app, sign-in and sessions – see Headless server |
| – | IC_MASTER_TOKEN |
– | Token with write access without a user account (at least 32 characters) – see Headless server |
| – | IC_SETUP_TOKEN |
– | Optional: secret required for the first setup (first admin account) – protects a freshly published, still empty instance from a stranger getting there first. Asked for in the setup form |
-media-cache-mb |
IC_MEDIA_CACHE_MB |
512 |
Disk cache for media in S3 in MB, 0 = off – see Storing media in S3 |
| – | IC_ENCRYPTION_KEY |
– | Key for encrypted credentials in the settings (at least 32 characters); without it IllusionCMS creates data/secret.key – see Storing media in S3 |
You set environment variables for REST sources ({env.NAME}) at startup as well. Variables with the prefix IC_ (CMS configuration, e.g. IC_MASTER_TOKEN) can never be read there. Workflows have no access to environment variables.
In addition: -version prints the version, -create-token "Name" creates an API token with write access, prints it and exits, -enable-admin re-enables an admin that was turned off in the settings, -healthcheck queries /healthz of the running server and exits with exit code 0 or 1 – intended for the HEALTHCHECK of a Docker container.
Docker and Coolify: Ready-made Compose files (standalone behind a reverse proxy such as Apache, and for Coolify) come with every release as illusioncms-vX.Y.Z-deploy.zip (including Linux binaries; instructions in the included README.md) and in the deployment repo IllusionCMS-Deployment. Important behind a proxy: the proxy must pass on the Host header (with Apache, ProxyPreserveHost On) – otherwise IllusionCMS can’t assign the website to the domain – and set X-Forwarded-Proto, so that the sitemap and robots.txt use https://.
Data folder and backup
data/
illusion.db database (pages, Snippets, CSS/JS, Collections, settings …)
illusion.db-wal database write-ahead log (part of it!)
media/2026/10/… uploaded and imported files
backups/ automatic backups: nightly without media (the last 7)
and before importing a bundle, including media (the last 3)
cache/media/ disk cache for media from S3 (only with S3)
secret.key key for encrypted credentials (without IC_ENCRYPTION_KEY)
Automatic backups: Every night from 3 am (server time), IllusionCMS backs up the complete content – pages, CSS/JS, Snippets, Collections, file settings … – without the media files themselves, but only if something has changed since the last nightly backup. The last 7 are kept. To restore: download the backup under Settings → Synchronization and import it again; the media folder stays unchanged. If the entries of Collections with Live data should also be reset to the state of the backup, check Also take over the entries of Collections with live data from the bundle when importing. It’s best to back up the media files additionally along with the data folder (they rarely change and are only deleted when you explicitly delete them).
Backup: The safest way is to stop the server briefly and copy the whole folder. While running: sqlite3 data/illusion.db ".backup backup.db" plus a copy of media/ – or simply download a bundle (contains all content, but no accounts and tokens).
Storing media in S3
By default, uploaded and imported files live in data/media. Optionally, IllusionCMS stores them in an S3-compatible storage – handy when the server has little disk space or the files should be backed up separately from the server. The URLs don’t change: files stay reachable under /media/…, IllusionCMS fetches them from the bucket itself (including ranges for videos and resumable downloads). The bucket stays private; it does not need to be publicly readable.
Where: Settings → Storage (in remote mode for the connected server). At the top you see the active storage, the number and size of the files and the fill level of the disk cache.
Fields:
- Endpoint – address of the S3 service, e.g.
https://s3.eu-central-1.amazonaws.com. - Region – e.g.
eu-central-1; with Garage usuallygarage, with Cloudflare R2auto. Empty =us-east-1. - Bucket – must already exist. Prefix (optional) – e.g.
cms/if several applications share one bucket. - Access key and secret key – the key needs permission to read, write, delete and list. The secret is never shown again; if you leave the field empty later, the stored one is kept.
- Path style – addressing
endpoint/bucket/fileinstead ofbucket.endpoint/file. Needed for Garage and endpoints with an IP address.
| Provider | Endpoint | Region | Path style |
|---|---|---|---|
| Garage (self-hosted) | http://127.0.0.1:3900 or your Garage address |
garage |
on |
| Hetzner Object Storage | https://fsn1.your-objectstorage.com (or nbg1, hel1) |
fsn1 |
off |
| Cloudflare R2 | https://<account-id>.r2.cloudflarestorage.com |
auto |
on |
| AWS S3 | https://s3.eu-central-1.amazonaws.com |
eu-central-1 |
off |
Test connection writes, reads and deletes a small probe object in the bucket and tells you clearly what is missing (address unreachable, key unknown, secret wrong, bucket missing, permissions missing). Save remembers the data – as long as the local storage is active, only as a draft.
Moving: Move to S3 … starts a guided migration in the background: all files are copied (what already exists at the target with the same size is skipped – an interrupted move continues where it stopped), then every copy is checked, and only then IllusionCMS switches over and purges the page cache. You can keep working in the meantime: uploads and deletions first go to the old storage, are recorded and carried over when switching – writes pause only for that short moment. With Cancel or on an error, the old storage stays active. If Delete the old files afterwards is checked, IllusionCMS cleans up the old storage after a successful switch; without it, the old storage stays as a fallback. Back to local storage … works the same way in the other direction. You change bucket and prefix of an active S3 storage only by migration; new credentials for the same bucket apply immediately after a successful test.
Disk cache: In front of S3 there is a local cache in data/cache/media (default 512 MB, start option -media-cache-mb or IC_MEDIA_CACHE_MB, per instance also in the storage settings). It mainly speeds up image variants and WebP and keeps delivering cached files when S3 is briefly unreachable. A bucket may only be used by one IllusionCMS instance (the cache assumes that all changes go through it) – for a second instance use a separate bucket or prefix.
Encrypted credentials: The secret is stored in the database only in encrypted form (AES-256-GCM). The key comes from the environment variable IC_ENCRYPTION_KEY (at least 32 characters, e.g. openssl rand -hex 32); if it is not set, IllusionCMS creates the file data/secret.key when first needed (readable only by the owner). Take the key with you when moving to another server – copy data/secret.key along or set the same IC_ENCRYPTION_KEY. If the key is missing or different, the secret can no longer be decrypted: IllusionCMS then starts with the local files and shows the error in the storage settings – enter the secret again and save, then S3 applies again. Bundles deliberately do not contain the storage setting.
What is encrypted and what is not: Encrypted with the same key in the database: the S3 secret, webhook secrets, the SMTP password, the IllusionDB tokens and the tokens of connections; old plain-text values stay readable and are encrypted on the next save. Stored secrets (SMTP password, IllusionDB token, S3 secret, connection token) are only reused for the same target – if host, address, endpoint or bucket changes, you have to enter them again. Not encrypted are the headers of REST sources (Authorization …): they are part of the content and therefore appear in bundles, nightly backups and when uploading to another instance – this is the only way to restore an instance completely. Treat bundles and backups like credentials (the data/backups folder and the media cache are readable by the server user only); credentials for sources can also come from environment variables ({env.SHOP_TOKEN}), in which case they are stored nowhere in the database.
Backups: Even with S3, the database stays local in data/illusion.db (as do the nightly backups). To back it up continuously into the same bucket, Litestream is a good fit – a separate program next to IllusionCMS that replicates the SQLite database continuously. Example litestream.yml (a complete Docker Compose example is in the README.md of the deployment package):
dbs:
- path: /data/illusion.db
replicas:
- type: s3
endpoint: https://fsn1.your-objectstorage.com
bucket: firma-media
path: litestream/illusion.db
access-key-id: ${LITESTREAM_ACCESS_KEY_ID}
secret-access-key: ${LITESTREAM_SECRET_ACCESS_KEY}
Restore with the server stopped using litestream restore -config litestream.yml /data/illusion.db. The S3 provider does not back up the media files themselves automatically – enable versioning or replication of the bucket at the provider if available.
Build locally, publish online (bundles)
You can build a website locally and then move it completely to your online server. A bundle is a file with the extension .icb (IllusionCMS bundle, technically a ZIP) containing the entire content of the instance: websites, pages, CSS/JS, Snippets, files, Collections, REST sources and redirects. Accounts and API tokens are not included – on the server, you keep signing in with your account there.
Everything you need is under Settings → Synchronization:
- Download – the bundle as an
.icbfile. - Upload and import – on the online server: choose the
.icbfile, Import and replace. Bundles and backups from older versions (.zip) can still be imported. - Sync with a server – from the local admin in one step, via a saved connection: Upload replaces the server’s content with the local one, Pull brings the server’s state including live data here (e.g. onto a new computer or after an emergency fix online). The progress is displayed. If something has changed on the side being replaced since the last sync, the CMS warns you beforehand – live data doesn’t count as a change.
Good to know:
- Importing replaces the complete content of the target – whatever was changed only online is gone afterwards. Beforehand, the server automatically backs up its previous state (the last 3 backups are available for download in the same view; restoring = importing a backup).
- Live data stays: Entries of Collections with the Live data checkbox (e.g. contact requests) are always kept on the target – including those that come in while you work locally. The local entries of these Collections are not transferred. In the other direction (importing a bundle from the live server locally, restoring a backup), you take them over with the checkbox Also take over the entries of Collections with live data from the bundle.
- Keeping content maintained online: With When uploading, keep all of the server’s Collection entries, the server’s entries (e.g. blog articles written online) stay – layout, pages and code still come from the local state. Matching is done via the Collection’s slug.
- Domains: Locally, your website usually has no domain. In that case, the website on the server keeps its configured domains. If you enter domains locally, they are taken over.
- Unchanged files are not rewritten – repeated transfers are faster. The whole bundle is still uploaded, though.
- Both instances should run the same IllusionCMS version; an older server rejects bundles from a newer version.
- If the server runs behind nginx,
client_max_body_sizemust be large enough for the bundle (Caddy: no limit). - Scriptable:
curl -H "Authorization: Bearer <token>" -H "Content-Type: application/zip" --data-binary @bundle.icb https://www.firma.de/api/v1/bundle(see API).
Multiple instances: work live or sync
Under Settings → Connections you enter other IllusionCMS instances – name, address and an API token with write access from there (on the server under Settings → API tokens; for a server without admin, the master token or one created via -create-token). The token stays in the local database, is not displayed and is not part of the bundle. Check shows whether the server is reachable, which version runs there and whether it runs headless. Whether the admin runs on the server itself doesn’t matter – all you need is the token. This way you manage any number of instances from one local app; headless is an additional option for servers that you maintain exclusively this way.
With a connection there are two ways of working – you can mix both:
- Work live: The top of the menu shows which instance the admin is currently working with (Local). If you choose a connection there, all views – pages, files, Collections, statistics, Workflows, 404 log … – go directly to this server; changes take effect immediately. The menu then shows the connection’s name with LIVE. This way you manage several websites on different servers from one local app, and you see live data such as contact requests directly. Only accounts, API tokens and the connections themselves stay local. Page preview, Pens and Workflow addresses point to the server.
- Build locally and sync: Try things out locally without visitors seeing anything, then upload under Settings → Synchronization (see above). Live data on the server is kept.
Versions: The admin app always comes from your local instance. When switching (and on every load), it checks whether the server runs the same IllusionCMS version. If it differs or the server isn’t reachable, the switcher turns red instead of yellow, and a notice names both versions; in the selection menu, each connection shows its version and, if applicable, a ⚠. You can still work, but some functions may be missing or fail – it’s best to update both instances together.
Live mode is suited for quick corrections, the local instance for bigger restructuring (new layout, many CSS changes) – drafts exist only for pages; a changed CSS file or Snippet takes effect live everywhere immediately.
Headless server (without admin)
If you manage your live server only from the local app, you don’t need an admin there. Without an admin, the server delivers the website, the API for tokens, the HTTP inputs of Workflows and public Pens – but no /cms-admin, no sign-in, no sessions and no account or token management (all 404). This reduces the attack surface: there is no login page that anyone could try to brute-force.
Simplest – without any user account: Set a master token at startup and turn off the admin:
IC_MASTER_TOKEN=$(openssl rand -hex 32) # note it down – this is the token for the connection
IC_HEADLESS=1
The master token counts as an API token with write access, but isn’t stored in any database and doesn’t need an account (at least 32 characters, otherwise the server won’t start). Locally, you enter it under Settings → Connections. In the history, it appears as the author Master-Token. It doesn’t manage accounts and tokens – that remains reserved for the admin. It is revoked as soon as you remove the variable and restart.
Turning off the admin via the switch: Instead of IC_HEADLESS=1, you can also use Settings → System → Disable admin – in the admin of the server itself or, more conveniently, from your local app in remote mode. This only works if a token with write access exists (or a master token is set), so that you don’t lock yourself out. To re-enable: in remote mode under Settings → System, via the API (PUT /api/v1/system) or on the server with illusioncms -enable-admin. Emergency exit if no token is known anymore: set IC_MASTER_TOKEN, restart, connect with it and enable the admin. With IC_HEADLESS=1, on the other hand, the admin is permanently off and the switch is locked.
Instead of a master token, a normal API token works too: create it on the server under Settings → API tokens while the admin is still running, or via the command line – in the Docker container e.g. docker exec <container> illusioncms -data /data -create-token "Lokales Admin".
Exporting as a static website
A website can be exported completely as static HTML pages – for simple web hosting with Apache, without IllusionCMS having to run there. In the editor of the home page under Settings → Website → Export as static website:
- Check export – renders all pages and shows a report: number of pages, files and size, pages with template errors, internal links leading nowhere, and missing files.
- Download ZIP and extract the content into the domain’s root directory (e.g.
htdocs/orpublic_html/).
What’s included:
- Every page as
pfad/index.html–/leistungenbecomesleistungen/index.html. Apache automatically redirects/leistungento/leistungen/. - Detail pages behind Catch-All pages (individual blog articles) are included if they are in the sitemap (Collections or sitemap source, e.g. IllusionDB) or linked somewhere – e.g. from the blog overview.
robots.txtas in the CMS.- The 404 page as
404.htmland an.htaccessthat includes it (ErrorDocument 404 /404.html) – with all redirects of the website. - All CSS and JS files,
sitemap.xml(for the website’s first domain or the specified base URL) and the images used, including srcset variants. With Include all files from the media library, the whole media folder comes along – necessary if JavaScript assembles image URLs itself. - Private files are always left out – even when all files of the media library are included – together with their variants and WebP versions; the report shows how many.
Limits: everything is a snapshot at the time of the export – REST data, Collections and later changes need a new export. Pages with query parameters (?seite=2) don’t exist statically, nor do /api/v1 calls from JavaScript. The export belongs in the domain’s root directory, not in a subfolder (links start with /).
Health check
GET /healthz responds without signing in with 200 and {"status":"ok", …} as long as the database is reachable, otherwise with 503. Suitable for uptime monitoring, Docker HEALTHCHECK or load balancers. The response also contains the build ID of the admin app and the Git commit of the binary – handy for checking whether an update is really running.
Behind a reverse proxy (HTTPS)
IllusionCMS itself only speaks HTTP. For HTTPS, put a proxy in front of it, e.g. Caddy (fetches certificates automatically):
www.firma.de, shop.firma.de {
reverse_proxy 127.0.0.1:8080
}
firma.de {
redir https://www.firma.de{uri}
}
With multiple websites, you enter all domains in the proxy; IllusionCMS selects the website based on the requested domain (the Host header, which Caddy passes on unchanged).
Then start the server with -addr 127.0.0.1:8080 -secure-cookies. The sitemap takes domain and scheme from X-Forwarded-Host/X-Forwarded-Proto, which Caddy sets automatically.
IllusionCMS can send protective headers such as HSTS itself (security headers). The proxy should make the redirect from http:// to https:// permanent (301 or 308, the default with Caddy). With Traefik, this requires permanent: true on the redirect middleware; otherwise it redirects with 302. For Coolify, there are instructions in the deployment package (README.md, section Coolify).
IllusionCMS compresses HTML pages, CSS/JS and the sitemap itself (gzip). Compression in the proxy is therefore not necessary. Better turn it off there; otherwise images get packed again needlessly too.
As a service (systemd)
[Unit]
Description=IllusionCMS
After=network.target
[Service]
ExecStart=/opt/illusioncms/illusioncms -addr 127.0.0.1:8080 -data /var/lib/illusioncms -secure-cookies
Environment=SHOP_TOKEN=…
Restart=on-failure
User=illusioncms
[Install]
WantedBy=multi-user.target