Sites and versions
Every publish is a new version. The owner can publish without going live, open a preview link to any kept version, make it live, roll back, rename a site (old links keep working), take it offline, put a passcode on it, and download a copy with its saved data. A deleted site waits in Recently deleted (see Cleanup and retention).
Size. MAX_ARCHIVE_MB bounds one upload and one site. It is a guard against one upload
filling a disk, not a budget: the median site on simple-host.app is about 25 KB. A proxy in front
needs its own body limit raised to match. MAX_ARCHIVE_MB_OVERRIDES gives named accounts their
own size; it holds new deploys only, so a larger site already live stays up. The admin page shows
each site’s live copy (what the limit is about) and its total on disk (with kept versions).
Versions kept. Every version is a full copy of the site, so on a small disk the history, not the sites, is what fills it. The installer keeps one version (no rollback). Hosted Simple Host keeps 4; selected accounts default to 10. Only operator-allowed accounts can change their per-site count.
Hosted new sites from the 2026-10-05 deployment have a 200 MB whole-file allowance, and accounts have 1 GB. Earlier sites have no total cap. The live copy and retained versions count; backend storage stays separate. Checks use the resulting total after pruning. Self-hosted defaults have no account or site-total cap. See Website files and versions for exceptions and compression tips.
Site passcodes. An owner may put one passcode on a whole site. Every address of the site
then shows a plain “This site is protected” page until the visitor enters it; a right passcode
lets that browser in on that one address until the passcode changes or the owner signs everyone
out. SITE_PASSCODES switches the feature; it also needs PASSCODE_ENC_KEY and a per-site
address (SITE_HOSTS and PERSON_HOSTS on). A server where every site shares one address (a
small box, an event box) refuses passcodes, because an unlock there would cover every site.
Generate the key once with openssl rand -base64 32 and keep it secret like ADMIN_API_KEY: it
seals every stored passcode so owners can read theirs back. Changing or losing it makes every
stored passcode unreadable and signs every visitor out; owners then set new ones.
PASSCODE_MIN_LENGTH is the shortest code allowed. Wrong tries are limited per address on a site
(RATE_LIMIT_PASSCODE_IP, then PASSCODE_LOCKOUT_MINUTES) and per site from everyone together
(RATE_LIMIT_PASSCODE_SITE, then PASSCODE_SITE_LOCKOUT_MINUTES); visitors already let in are
not affected. A site with a passcode has a passcode file next to current in its folder, so
addresses nginx or Caddy serve straight from disk hand the request to the app instead. The
shipped vhosts and deploy/prod/nginx-suspended-marker.sh already do this; a hand-made nginx
vhost that serves a site folder needs this line after its offline check:
if (-f <site folder>/passcode) { rewrite ^ /internal/passcode$uri last; }
and, when the block has no /internal/ proxy to the app, a location ^~ /internal/passcode/
one. nginx-suspended-marker.sh adds both to every block that serves a site folder.
Named viewers use the same marker and hand-off. An owner may open a whole site only to people
named by email; each signs in on the site with that email (visitor sign-in, so the server needs
email codes or Google set up), and the list is checked on every request. Like a passcode it needs
a per-site address (SITE_HOSTS and PERSON_HOSTS on); a server where every site shares one
address refuses it, because a sign-in there would cover every site. A site has named viewers or a
passcode, never both. SITE_VIEWERS_MAX caps the list per site.
| Setting | Default | Allowed | What it does |
|---|---|---|---|
IMPORT_LINK_TTL_MINUTES |
30 |
1–1440 minutes | How long a WordPress import page link works. Security-sensitive. |
IMPORT_MAX_UPLOAD_MB |
1024 |
1–10240 MB | Maximum bytes in a WordPress import upload, in MiB. |
IMPORT_MAX_XML_MB |
100 |
1–1024 MB | Maximum combined WordPress XML bytes, in MiB. |
IMPORT_MAX_ARCHIVE_ENTRIES |
20000 |
1–100000 count | Maximum entries across import archives. |
IMPORT_MAX_UNPACKED_MB |
2048 |
1–20480 MB | Maximum unpacked import bytes, in MiB. |
IMPORT_MAX_CONCURRENT |
1 |
1–16 count | WordPress imports converting and publishing at once. |
IMPORT_FETCH_MAX_MB |
40 |
1–200 MB | Maximum bytes fetched for one import file, in MiB. |
IMPORT_FETCH_TOTAL_MB |
1500 |
1–10240 MB | Combined download budget for one import, in MiB. |
MAX_SITES_PER_ACCOUNT |
100 |
1–100000 sites | Sites one account may hold (sites in Recently deleted count). |
MAX_SITES_OVERRIDES |
none | handle:sites | Accounts that may hold a different number of sites than MAX_SITES_PER_ACCOUNT: comma-separated <handle>:<sites>, e.g. chhotabreak:2000 (1 to 100000 each). It follows the handle: an account that changes its handle keeps its override under the old one. |
MAX_ARCHIVE_MB_OVERRIDES |
none | handle:mb | Accounts whose sites may be a different size than MAX_ARCHIVE_MB: comma-separated <handle>:<MB>, e.g. jot-transcribe:300 (1 to 500 each). It applies to new deploys only (a larger site already live stays up) and follows the handle like MAX_SITES_OVERRIDES. A proxy in front must accept bodies this large (4/3 of it for JSON and connector deploys), and the connector takes messages that large from every account, so keep values modest. |
KEEP_VERSIONS_OVERRIDES |
none | handle:versions | Account defaults in place of KEEP_VERSIONS, as handle:n (1 to 1000). Current and old handles match. |
MAX_SITE_TOTAL_OVERRIDES |
none | handle:mb | Account site-total limits in MB, as handle:MB (1 to 1000000); current and old handles match. |
MAX_ACCOUNT_MB_OVERRIDES |
none | handle:mb | Account file allowances in MB, as handle:MB (1 to 1000000); current and old handles match. |
MAX_SITE_TOTAL_MB |
0 |
0–1000000 MB | Whole website footprint after deploy and pruning: live files and all kept versions, including the separate live copy. 0: no cap. Applies only to sites created on or after SITE_TOTAL_CAP_FROM. Saved data and storage resources have separate limits and do not count. |
MAX_ACCOUNT_MB |
0 |
0–1000000 MB | All account website files and kept versions, including Recently deleted sites and the separate live copies, after deploy and pruning. 0: no cap. Saved data and storage resources do not count. |
KEEP_VERSIONS_SELF_SET |
none | handles | Handles allowed to change per-site version counts, comma-separated; current and old handles match. Unset: anyone may set their count. Other accounts use the default even if a site previously had its own setting. |
SITE_TOTAL_CAP_FROM |
none | RFC3339 | RFC3339 creation cutoff for MAX_SITE_TOTAL_MB. Earlier sites are grandfathered. Unset: no site total cap. |
MAX_FILES_PER_SITE |
50000 |
100–50000 files | Files in one upload. It can only be lowered. |
PREVIEW_LINK_TTL_MINUTES |
60 |
5–10080 minutes | How long a preview link to a stored version works. Security-sensitive. |
EXPORT_LINK_TTL_MINUTES |
10 |
1–60 minutes | How long a site or event archive download link works (it can hold private data). Security-sensitive. |
SITE_PASSCODES |
on |
on / off |
on lets owners put a passcode on a site (it needs PASSCODE_ENC_KEY and a per-site address). off refuses new ones; sites that already have one keep asking for it. |
PASSCODE_MIN_LENGTH |
6 |
4–64 characters | Shortest passcode an owner may set, in characters. Any characters count; digits only is fine. |
SITE_VIEWERS_MAX |
50 |
1–1000 viewers | Most named viewers (people who may open a site that is open only to named viewers) one site can have. |
PASSCODE_LOCKOUT_MINUTES |
15 |
1–1440 minutes | How long one address is refused on a site once it has used up RATE_LIMIT_PASSCODE_IP. |
PASSCODE_SITE_LOCKOUT_MINUTES |
15 |
1–1440 minutes | How long a site refuses every passcode try once RATE_LIMIT_PASSCODE_SITE is used up (visitors already let in are not affected). |
RATE_LIMIT_UPLOAD |
30,10s |
any (warns past 10× looser) | Uploads and deploys per client. |
RATE_LIMIT_SITE_OPS |
30,2s |
any (warns past 10× looser) | Deleting, changing and restoring sites, and admin sign-in tries, per address. |
RATE_LIMIT_EXPORT |
10,10s |
any (warns past 10× looser) | Site and account downloads per address. |
RATE_LIMIT_ANALYTICS |
30,2s |
any (warns past 10× looser) | Top pages and referring domains reads, per address. |
RATE_LIMIT_PASSCODE_IP |
5,3m |
stricter freely; loosest 20,45s |
Wrong passcode tries on one site per address. Security-sensitive. |
RATE_LIMIT_PASSCODE_SITE |
60,1m |
stricter freely; loosest 240,15s |
Wrong passcode tries on one site from everyone together. Security-sensitive. |
PHOTO_MAX_BYTES |
1000000 |
at least 1 bytes | Maximum bytes per prepared photo; the MCP picker also caps at 1 MiB. Positive integer; invalid values use the default. |
PHOTO_MAX_DIMENSION |
2048 |
at least 1 pixels | Longest allowed side of a prepared photo. Positive integer; invalid values use the default. |
PHOTO_MAX_CONCURRENT |
3 |
at least 1 saves | Concurrent photo saves across the process. Positive integer; invalid values use the default. |
PHOTO_BODY_TIMEOUT_SECONDS |
60 |
at least 1 seconds | Read deadline for a photo-link upload body. Positive integer; invalid values use the default. |
MAX_ARCHIVE_MB |
100 |
at least 1 MB | Largest upload, and the size one site may have. |
KEEP_VERSIONS |
0 |
at least 0 versions | Deploys kept per site; 0 keeps all. install.sh sets 1, which means no rollback. |
PASSCODE_ENC_KEY |
none | secret | Seals site passcodes, notification signing secrets and custom header tokens with AES-256-GCM (32 random bytes, base64: openssl rand -base64 32); without it new passcodes and notifications are refused, and changing it makes stored passcodes and notification secrets unreadable and signs every visitor out. Security-sensitive. |
PREVIEW_ACCOUNTS |
none | text | Accounts (comma-separated) whose sites expire on their own. |
WEB_PUSH_ACCOUNTS |
none | text | SIM-360: accounts (comma-separated handles) allowed to use Web Push as a notification channel; push routes 404 for everyone else. “all” or “*” enables it for every account. Empty: off. |
PREVIEW_TTL_HOURS |
48 |
at least 1 hours | How long those accounts’ sites last. |
Recipes
The installer’s defaults already fit: --max-site-mb 100 --keep-versions 1. To cap each participant too:
MAX_SITES_PER_ACCOUNT=20
Keep rollback on a bigger disk. Install with --keep-versions 5.
Existing-site summaries after an upgrade
After upgrading to a release with zi-site-search-summary.sql and running
migrate, populate summaries from the existing active site files. The command
only updates sites without a summary, so reruns are safe. Run it in the app
container, whose environment and site volume are already configured. No key or
connection string needs to be copied.
Small-box installer and DigitalOcean installations:
docker compose --project-directory /opt/simple-host --env-file /opt/simple-host/.env run --rm --no-deps -T app site-summary-backfill
For either Coolify recipe, open the app service’s terminal after migration and
run simple-host site-summary-backfill. For a manual Compose checkout, run
docker compose run --rm --no-deps -T app site-summary-backfill from its directory.
The old pinned releases do not contain this command; run it after installing
the new release. New deploys populate summaries automatically.
Add photos
PHOTO_MAX_BYTES sets the maximum bytes per prepared photo (default 1,000,000). PHOTO_MAX_DIMENSION sets the longest allowed side in pixels (default 2048). PHOTO_MAX_CONCURRENT bounds photo saves across the process (default 3). PHOTO_BODY_TIMEOUT_SECONDS sets the body-read deadline at both the owner and photo-token entry points (default 60 seconds; positive integers). Unsupported response writers continue without a deadline. Both signed-in and token saves acquire a slot before reading the request body; full capacity returns 429 “Busy, try again in a moment” and Retry-After: 2 without queueing. A slot is held through commit. Each request still allows 30 photos of 1,000,000 bytes plus bounded multipart overhead by default. The Add photos page prepares small WebP or JPEG copies on the device. The server validates them without decoding pixels or resizing. Each request takes up to 30 photos; the normal site upload, retained-version, account, and deploy rate limits also apply.
Upload and import behavior
MAX_SITES_PER_ACCOUNT applies to non-admin accounts; exceeding it returns
403 site_quota_reached. Malformed MAX_SITES_OVERRIDES or
MAX_ARCHIVE_MB_OVERRIDES entries stop startup.
MAX_ARCHIVE_MB caps the upload and its uncompressed live copy, excluding kept
versions. Archive overrides apply on every deploy path, including admin deploys.
Existing larger sites can still be rolled back to. /v1/me and
GET /v1/admin/users report the account cap as max_site_mb; exceeding it
returns 413 site_too_large. JSON and connector deploys carry base64, hence the
proxy body allowance of 4/3 of the largest override noted in the table.
The upload pipeline and disk are sized for at most 50,000 files. When
MAX_ARCHIVE_MB is set, the effective file cap is the smaller of
MAX_FILES_PER_SITE and one file per 4 KB of that budget.
create_import_link creates a link spent by one import. The proxy must accept
IMPORT_MAX_UPLOAD_MB request bodies on /v1/imports/. Up to five imports wait
behind the concurrent conversions; further requests ask the person to try again
in a few minutes. Downloaded import files include photos missing from the media
file and theme stylesheets. Export links are reusable and may hold private
collections, so their lifetime is capped at an hour.
The longest site passcode is 128 characters, independently of the configured minimum.