Chapter 09
Limits and reference
The numbers you will eventually hit, and a table for when something is not behaving.
Limits, PHP settings and paths below describe catalog sites — they come from the platform's three built-in images. Any other image on your box — an administrator can upload one, as chapter 03.1 describes — brings its own versions and its own defaults, and the panel's picker is what tells you it is there. A custom stack brings its own images too, so its runtime, its limits and its timeouts are whatever you put in them. The stack-specific reference is at the end of this page.
Request and upload limits
| Setting | Value | What hitting it looks like |
|---|---|---|
| Maximum upload size | 64M | Large uploads rejected by the web server before PHP sees them |
| POST body size | 64M | Same, for form submissions |
| Maximum execution time | 60s | Long request killed mid-flight |
| Request timeout at the web server | 60s | A 504 even if PHP is still working |
| Maximum input variables | 5000 | Very large forms silently truncated |
| Default PHP memory, per request | 256M | Fatal "allowed memory size exhausted" — adjustable per site |
| Default requests at once | 8 | Requests beyond it queue rather than fail — adjustable per site |
| Session lifetime | 7200s | Users signed out after two hours idle |
Both timeouts are 60 seconds, so anything genuinely long-running belongs in a queue worker rather than a web request. The last two rows are the only ones you can adjust per site, and they are the same two settings chapter 03 describes.
PHP environment
- Version
- PHP 8.3, served by php-fpm behind nginx.
- Extensions
mysql,mbstring,xml,bcmath,curl,zip,gd,redis,opcache,intl- Tooling
- Composer, git, unzip, and the PHP CLI are available inside the container.
- Timezone
UTC. Set your application's own timezone in its configuration if you need another. This is the clock inside your container; the panel keeps a server timezone of its own, further down.- Error display
- Off. Errors are logged to the container output, never rendered to visitors.
- Opcache
- Enabled, and does not re-check file timestamps. Restart the site after deploying PHP changes.
- Concurrency
- Eight PHP worker processes per site by default — the "Requests at once" setting — recycled every 500 requests.
What your site is using
Resource usage in the panel's menu lists every site you can see — your own, if you are a site owner — with its memory, CPU, peak and disk, heaviest first, and the server itself on a line of its own. Open a site from that list for its charts, over 6 hours, 24 hours, 7 days or 30 days, and for the one control that lives there: a memory limit.
Readings are taken once a minute, and the page says how old its figures are. "Not measured" and "Unknown" are real answers, not errors to work around — they mean the measurement has not run recently, or that nothing has measured this yet.
- Memory, and the second memory figure
- Two numbers, and the difference between them catches everyone: the working figure, and a larger one including file cache — pages the kernel is holding on your behalf and would drop under pressure. A limit is enforced against the larger number, so that is the one to compare a limit against.
- Peak
- The highest reading ever taken, not a guaranteed maximum. A spike shorter than a minute happens between two readings and never appears. Use it to choose a limit; do not read it as proof of what the site can reach.
- Disk
- It comes from the site's backup — measuring it is a side-effect of taking one — so it is the figure from the last night this site was copied, which is not every night if you have narrowed its backup days (below). A site with backups turned off reads Not measured rather than
0 B, and a site created today shows nothing until the morning after its first backup. - Platform services
- The shared MySQL, Redis and Caddy are listed on their own, for administrators, and their usage is not attributed to any site. A query-heavy site inflates MySQL rather than its own row, which is why the site rows never add up to the server total.
- CPU, which is two different numbers
- A site's CPU is a percentage of one core, so a busy site using several of them legitimately reads over 100%. The server's own figure is the share of the whole machine that is busy, between 0 and 100, and never passes it. Comparing the two directly means nothing.
- The server's own line
- The machine reports its processor first, then memory in use against total, swap in use against swap total, and disk. The processor figure is an average over the last minute rather than a live reading, and the page says so; memory and swap are live. A server with no swap configured says that in words rather than showing
0 B / 0 B, which would read as a broken measurement. There is no per-site swap figure: swap is a property of the whole server.
Limiting a site's memory
Empty is the default, and empty means no limit. Give it a figure — 512M, 1G — and the server stops the container when it reaches that and starts it again. The point is blast radius: one site cannot exhaust the machine and take every other site down with it.
Saving the figure records it. The container keeps running under its old limit until it is recreated — press Recreate, in the Container box above the site's tabs (chapter 02.1), and any change that recreates the container anyway does it too. A restart does not.
Choose the figure from the site's own peak, with headroom above it. A limit that is too small does not warn you: the site is stopped and restarted, again and again, exactly when it is busiest.
The control panel itself has no limit and cannot be given one. That is deliberate — the panel is what you use to fix everything else, so it is never the process that gets stopped.
Reporting to a cockpit
If your operator runs more than one of these servers, they can link this one to a cockpit: a separate page that watches several installations at once and shows each one's version, its processor, memory and disk use, and whether it is still reporting. It is a tab under Settings and only an administrator sees it. On a single installation the tab is there, says what it would do, and can be left alone.
Linking one takes three steps: open the cockpit, create a pairing code there, then paste the cockpit's address and that code into Settings → Cockpit. A code works once and then expires, so it is made at the moment it is needed rather than kept somewhere.
What leaves the server is listed on the page itself, rather than summarised:
- The installation's name, identifier, version and address.
- The processor, memory, swap and disk figures described above.
- How many sites there are, and how many containers are running.
A cockpit may watch several people's installations, and a list of sites is a list of customers. Counts go; names do not.
The panel does the calling, always. A cockpit never contacts the server — which is the opposite of the usual assumption about a feature that watches machines. Linking one opens no port and needs no change to a firewall, and nothing at the cockpit end can act on this server: it reads the figures the panel chose to send it, and there is nothing else for it to reach.
A report goes every five minutes, so the cockpit's figures can be up to five minutes behind the ones on this server — worth knowing before you conclude the two disagree. A cockpit that is down changes nothing here: sites, backups and everything else carry on, and the Settings tab says when a report was last accepted and why the last attempt failed. Unlinking works whether the cockpit answers or not, and the panel stops reporting immediately either way.
Which nights your site is backed up
A site is copied at night, in one run at the platform's backup time. Its Backup tab carries a Backup days box above the retention line: seven weekday buttons saying which nights this site may be taken on. Anyone who can open the page can set them, site owners included.
The reason to narrow them is that a site pauses while its copy is taken. Visitors get a "temporarily unavailable" page for a few seconds, and a stack is stopped for that moment. A site whose own nightly jobs run at the same hour, or one nobody wants blipping on a weekday morning, can move to other nights. It is not a way to make a backup faster or smaller, and fewer nights means fewer copies to go back to.
- Not set
- Where every site starts: all seven buttons selected, a Not set label beside them, and a line saying the site has no days of its own. It follows the platform, and nothing needs doing about it.
- Days of its own
- Once you save, the label goes and a Back to the default button appears, which puts the site back to following the platform. Saving all seven days looks identical to Not set in the buttons, on purpose — the label is what tells the two apart.
Clearing every day and saving is refused. A site with no backup night is never copied, and the copies it already has expire on their own, so the panel does not let a blank form say that by accident. There is no switch here for turning a site's backups off, and that is deliberate.
Nothing else about the run changes. On a night a site is not due, it is simply not in the run: nothing pauses, and nothing appears in its list of copies for that date. There is no skipped entry to look for.
How many copies are kept is a count and not a length of time — the page says how many of the last copies it keeps for this site. Seven copies of a site taken twice a week reach back three and a half weeks; seven copies of a nightly one reach back a week. Read the number as copies and work the span out from the days you chose.
The platform's own copy — the panel's database, its secrets, the certificates — is taken every night whatever any site's days say, and it takes nothing offline while it runs.
The clock everything runs on
Every time the panel prints or schedules is in the server timezone: the backup time and the days above, run history, the resource-usage charts, the emails it sends. It is one setting, on Settings → General, and only an administrator can change it. An installation where nobody ever set one is on UTC, which the page says rather than leaving you to assume.
The same page shows the server time. That is read from the panel and not from the computer you are sitting at, which is what makes it the way to check a change took effect — and it is the clock the backup time refers to.
Everything the panel displays follows a new zone straight away. The nightly schedule follows it only once the panel has been restarted, which is the panel owner's job. Expect the two to disagree until that happens.
This is a different thing from the timezone inside your own container, which is UTC and is changed in your application's configuration, as above.
Taking a copy of a backup away
Every site has a Backup tab listing the copies taken of it, and each row offers two things. Restore this copy puts the site back as it was. Download backup gets the copy off the platform entirely, which until now needed a shell on the server. Both are yours to press on your own sites; neither is administrator-only.
Downloading does not hand you a file there and then. It goes like this:
- Press Download backup on the copy you want. A dialog says what is about to happen; press OK.
- The page comes straight back, and the copy is prepared as a zip in the background. A large site takes a while.
- An email with a link arrives when it is ready, and the same link appears on that row of the Backup page. Either one works — the row is there for the day the email goes astray.
The zip holds everything that copy holds: your files, the .env, the database dump and the site's TLS private keys. That is what makes it worth having, and it is the warning as well. Treat the file the way you treat a password, keep it where you would keep one, and delete it when you are done.
The link lasts 72 hours. After that the file is deleted from the server and the row stops offering it — not archived, not hidden. If you need it again, ask again; preparing a second copy of the same backup is an ordinary thing to do.
It is tied to the account that asked for it. Signing in as somebody else and opening it does not work, and that includes an administrator, and the site's owner if the owner is not you. The random characters in the filename only stop it being guessed; what admits you is your own session.
It does work from anywhere, deliberately: you should be able to open it wherever you read your email, including from an address the allowlist has never seen (chapter 08).
One preparation runs per site at a time. Asking again while a site is already preparing one is refused, so wait for the email. Two people can each prepare their own copy of the same backup, and each gets their own file.
If it cannot be prepared, an email tells you the download is unavailable at the moment. It does not say why — administrators are told the reason separately, and the usual one is not enough free space on the server, which somebody may have to clear before you try again. Nothing about the site or its backups changes either way.
When a backup fails
Backups are a platform-wide job, and the Backups page belongs to administrators. What matters to everybody else is that a failure is no longer only visible to somebody who thinks to look: every administrator account is emailed once per failed run.
- What counts as a failure
- A run in which everything failed, and a run in which some things failed while others succeeded. The second is the ordinary case — one site's database dump fails at 03:15 and the other twelve capture fine — and it sends mail exactly as the first does.
- What does not
- A site that was deferred because it was mid-deploy when the run reached it. That is not a failure: the site is taken on the next run and nobody is emailed.
- One email per run
- A run with four failed sites sends one message naming all four, not four messages. Re-running a backup by hand produces a second email only because it is a second run.
- Who gets it
- Every administrator account, worked out when the message is sent. Promote an account and it starts receiving them; demote it and it stops. There is no subscription list and nothing to configure. Site owners and unlock-only accounts are never emailed — a site owner cannot open the Backups page at all.
- What is in it
- Which installation it came from, when the run started and finished, each failed unit with the reason recorded for it, how many succeeded and how many were skipped, and a link straight to that run.
The panel hands the message to a mail service, and that is the end of what it knows. There is no delivery receipt, no bounce notice and no send history to look through, so the panel can say a notification was sent and can never confirm anybody received it. If an administrator is not getting them, the things to check are the address on their account and their spam folder; nothing in the panel will tell you what became of a particular message.
The mail credential lives in the server's own configuration file rather than in the panel, so there is deliberately no field to paste one into and only your operator can switch this on. Until they do, the Settings page says so plainly and backups keep running, failing and recording their failures exactly as before. Nothing about mail can change whether a backup succeeded or what it reported. The same credential carries account invitations (chapter 08) and backup download links, so a server with one has all three and a server without has none of them.
Paths
- Site folder
/var/www/virtual/<your-domain>/— identical inside and outside the container- Docroot, file-serving images
- the folder itself — the same for Static Website and Single-Page App
- Docroot, PHP image
<folder>/application/public- App root, PHP image
<folder>/application— the deployed app (repo contents,vendor/,artisan); wiped and re-published on each deploy- Durable data, PHP image
<folder>/storage(Laravel storage tree) and<folder>/.env(secrets) — kept outsideapplication/and symlinked into it, so they survive a re-publish- Background programs
<folder>/application/.nimbus/supervisor/— the known namesboot.conf,queue-worker.confandscheduler.conf, plus every*.confin itsdaemons.d/subdirectory- A folder mounted from another site
- Wherever the mount says, and never inside the site's own folder —
/mnt/<something>by habit. Applied when the container is created, so a new one needs a Recreate (chapter 06) - Health endpoint
/health— reserved; do not define a route at this path- SFTP
- Port
2223, SSH key only and no password, username is your email address. You get one directory per site you hold, worked out at connect time, and the port is not behind the IP allowlist (chapter 04)
Service addresses
- MySQL
mysql:3306from a catalog site,mysql.nimbus:3306from a stack — version 8.0. Provision the database and user yourself from the site's Databases tab.- Redis
redis:6379from a catalog site,redis.nimbus:6379from a stack — version 7. Persistent, does not evict; set expiry on cache entries.- Your site
no-<domain-with-dashes>on the internal network, port 80. No published host ports.- Your stack
- Containers named
stk-<slug>-<service>-N, on the project's own network. The ports you declare as${NIMBUS_BIND}:${NIMBUS_PORT_*}are reachable from the edge and nowhere else. A port an administrator has published on the server is the one exception, and it is reachable by anyone.
Your box states these itself, on System information → Technical — the two host pairs, the site and stack folders, and the certificate authority it issues from. Where this page gives the platform's defaults, that one gives your box's actual values. Chapter 08.
Custom stacks
- Compose file
compose.yamlordocker-compose.yml, at the repository root. Validated on every deploy.- Project name
stk-<slug>, owned by the panel. Prefixes every container, network, volume and locally-built image.- Stack directory
/var/www/stacks/<slug>/—application/(the clone),.env(symlinked into it),.nimbus/override.yml(panel-owned)- Allocated ports
- Declared in the compose file as
"${NIMBUS_BIND:-127.0.0.1}:${NIMBUS_PORT_<NAME>:-8080}:<container-port>"— anything else is refused. Allocations are per name, sticky across deploys, released on delete. Reachable from the edge proxy only. - Published ports
- A host port forwarded straight to a service's container port, added by an administrator on the Ports page. Host port from 2200–2299 by default; stacks only; no TLS and no IP allowlist, because the edge proxy is not in the path. Adding or removing one recreates that service. Chapter 02.2.
- Injected at deploy
COMPOSE_PROJECT_NAME,NIMBUS_BIND, oneNIMBUS_PORT_*per name, andextra_hostsformysql.nimbus/redis.nimbus- Not permitted
container_name, top-levelname, external volumes, hostnetwork_mode/pid/ipc/userns_mode,privileged/cap_add/devices/security_opt, bind mounts outside yourapplication/, variables in a volume source
The HTTP API
One endpoint is documented for you to call directly: the one that unlocks the address a call arrives from. It is what makes an unlock possible from a machine, on a timer, with no browser involved — chapter 08 covers when you would want that and how to get a token for it.
- Endpoint
PUT /api/v1/allowlist/address, on the panel's own domain. That domain is never lockable, so it answers from anywhere.- Authentication
Authorization: Bearer <token>. Tokens are issued on Your account → API tokens, by any account whatever its role, and shown once.- Body
{"label":"Home office"}, sent asapplication/json.labelis required and is the name the address appears under on Unlocked IPs.- Which address it unlocks
- The one the request came from, always. There is no address field, and supplying one anywhere in the request changes nothing.
- Rate limit
- 10 calls a minute from one address.
| Answer | What it means | What to do |
|---|---|---|
| 200 · "enforced": true | The address is on the list and in force at the proxy. | Nothing. Locked sites serve you immediately. |
| 200 · "enforced": false | Saved, but not yet in force — the proxy has not taken it. | Repeat the same call. A script that checks only the status code will report success while the site still refuses you. |
| 401 | The token is wrong, or it has been revoked. The same answer is given for every such case, deliberately. | Issue a new token. Do not expect the reply to say which it was. |
| 429 | More than ten calls a minute from your address. | Call less often. A quarter-hourly timer is nowhere near this. |
Troubleshooting
| Symptom | Most likely cause |
|---|---|
| Deployed a fix, old behaviour persists | Opcache. Restart the site — see chapter 04. |
| Edited a Blade template, old markup still renders | Also opcache. The compiled template keeps the same filename, so the cached copy wins. Restart the site. |
| Page says "image OK (no app deployed)" | Nothing at the docroot. On the PHP image your entry point must be at application/public/index.php. |
| Status is Up but the site errors | Expected — the health check never runs your app. Check the container logs. |
| Site restarts by itself when it gets busy | Its memory limit is too small, so the server stops the container and starts it again. Raise it above the site's peak — see above. |
| A folder from another site is not there, or is empty | Either the mount has not been applied yet — that takes a Recreate, not a restart — or its source is gone, which the site's Mounts tab reports as Source missing with the path it looked for. Nothing is ever created in place of a missing source. Chapter 06. |
| Writing to a mounted folder fails | The mount is read-only, which is the default. Only the site that produces the files is given write access, and changing that is an administrator's call. |
| Deleting a site is refused | Another site mounts a folder of it. The message names them; those mounts come off first — chapter 06. |
| Blank 403, no styling | Your IP address is not unlocked. See chapter 08. |
| The panel's front page offers no way in, only "Unlock my address" | Working. You are at an address that is not unlocked, so the button that would refuse you is withheld and the one that fixes it is shown. Press it — chapter 08. |
| A site you know exists shows "not found" | It is not filed under your account. Another owner's site is absent from your panel rather than marked private, so it reads exactly like a deleted one — chapter 08. |
| An unlock call succeeds nightly but the site still refuses that machine | The reply is carrying "enforced": false and the script is only reading the status code. Repeat the call and check the field — see above. |
An unlock call that worked yesterday answers 401 |
The token was revoked, or the account holding it was deleted. Either also closed the address it had opened. Issue a new token. |
| SFTP fails naming a host you have never heard of | The @ in your username was not quoted, so the client took the part before it as a machine name. Write 'you@example.com'@your-server — chapter 04. |
| SFTP refuses your password | There is no password to refuse. The service takes an SSH key and nothing else; add the public half on Your account → SSH keys. |
| The Users, Backups or Settings pages refuse you | Those are administrator-only. A site owner gets their sites and nothing platform-wide; the refusal is a styled panel page, not a blank 403. |
| Certificate warning or HTTPS failure after publishing | DNS not resolving to the box yet, so the certificate is still pending. On a dev box, an untrusted authority is normal. |
| Every visitor shares one rate limit | Trusted proxies not configured, so every request carries the proxy's address. See chapter 07. |
| Every log entry shows the same IP address | The same cause, and the same fix — the address being recorded is the proxy's. |
Generated links and redirects come out http:// |
Also trusted proxies: the app cannot see X-Forwarded-Proto, so it builds URLs from the plain HTTP it was handed. |
| App cannot connect to the database | The database was probably never provisioned — do it from the site's Databases tab. From a stack, also check the host is mysql.nimbus and not mysql. |
| Stack deploy refused with a message about the compose file | Your repository uses a construct the platform does not allow, or publishes a port in the wrong shape. The message names the service and the key — see chapter 02.2. |
| Stack domain resolves but serves nothing | Almost always a port bound to literal 127.0.0.1 instead of ${NIMBUS_BIND}. Caddy is a container and cannot reach the host's loopback. |
| Nothing answers on a stack's published port | The panel opens the port; the service still has to listen on it and, for something like Gitea, be told to advertise it. Check the service's own configuration and redeploy — chapter 02.2. |
| Queue worker not running | Filename must be exactly queue-worker.conf; then restart the site. Check the log for a SKIPPED line. |
| A program of your own never starts | It has to be a *.conf file inside .nimbus/supervisor/daemons.d/ — loose in .nimbus/supervisor/ under a name the image does not know, it is ignored without a word. Then restart the site. If two files declare the same [program:…] name, only one of them runs. |
| Worker rejected on start | Invalid INI, a non-program section, or user= set to something other than www-data. |
| Uploads fail over ~64 MB | The upload limit. Not adjustable per site. |
| Request dies at 60 seconds | Execution or gateway timeout. Move the work to a queue. |
| Assets stale after deploy | Static files carry a one-year immutable cache header. Fingerprint asset filenames. |
What this platform does not do
Stated plainly, so you can plan around it rather than searching for a setting that is not there:
- No build step for a catalog site. Files are copied in and the site is restarted — or the in-container runner does it on push (chapter 10). Building images is a stack's job.
- No deploy-on-push for a stack. Deploys are a button in the panel, because they build images and the runner deliberately cannot reach Docker.
- No renaming a stack. The slug is baked into every path and label — delete and re-create.
- No custom environment variables beyond the settings an image declares. Your own configuration goes in the site's
.env. - No wildcard certificates. Every domain needs its own DNS record.
- No say over what is backed up or how long a copy is kept — retention and storage are settings on the server, and administrators hold them. What you can do with a copy of your own site is restore it or download it, as above. Deleting a stack still destroys its volumes with no undo.
- No self-service sign-up and no password reset. An administrator creates every account, either typing a password or emailing an invitation the person uses to set their own (chapter 08). The platform sends three kinds of email and no others: an invitation, the failed-backup notice above, and the download link above that.
- No log viewer in the panel, except a stack's deploy output. Container logs are read from the box.
The project has a design for automatic banning of probing addresses. It does not exist in the running platform. If you come across it in the project's design documents, read it as intent rather than as behaviour you can rely on.