Node.js and Python apps
Run Node.js, Python, Go and other server apps behind nginx with automatic ports, start commands, zero-downtime blue/green releases, multi-core Node.js and live logs.
Server applications (Node.js, Python, Go, Ruby, Java, .NET) run as a service of the hosting account, behind nginx, which handles the domain, SSL and static files. ZoPanel builds and starts them through the website's Deploy tab, keeps them running, and restarts them if they crash.
Install the runtimes
Administrators install language runtimes under Runtimes:
| Runtime | Versions offered | Used for |
|---|---|---|
| Node.js | 24, 22, 20, 18 (several side by side) | Next.js, Nuxt, NestJS, Express and front-end builds. pnpm and yarn are included. |
| Python | The system Python 3, with virtual environments | Django, Flask, FastAPI |
| Go | Latest | Go services |
| Composer | Latest | Laravel and Symfony |
| Ruby, Java, .NET | System packages (.NET 8.0) | Rails/Rack, Spring Boot, ASP.NET Core |
If a project asks for a Node.js major version that is not installed (in .nvmrc, .node-version or engines.node), the newest installed version is used.
Create the website
You have two options:
- Git deploy (recommended): in Websites → New website, choose Git deploy and enter the repository. See Git deploy.
- Node / Proxy: choose Node / Proxy to create a website that forwards every request to an application port, then set up the app on the Deploy tab with Uploaded files (File Manager): upload your project to
domains/<domain>/sourcewith the File Manager or SFTP and click Deploy.
Ports
Your app must listen on 127.0.0.1 at the port given in the PORT environment variable. ZoPanel sets it for you:
- Each website gets its own base port, shown as Internal port on the Deploy tab (
127.0.0.1:<port>). - For blue/green releases, the second slot uses the base port plus 10000. Never hard-code a port: always read
PORT. - On customer accounts, the proxy port of a website is fixed by ZoPanel and cannot point to another local service. Only administrators can set a different Application port on the PHP & config tab.
Examples:
// Node.js (Express)
app.listen(process.env.PORT, "127.0.0.1");
# Python: bind the server to $PORT, e.g. in the start command
# .venv/bin/gunicorn myproject.wsgi:application --bind 127.0.0.1:$PORT
Start commands
ZoPanel proposes the start command from the project. You can change it in Build & run → Start command (one line; chain steps with &&).
| Framework | Proposed start command |
|---|---|
| Next.js | npm run start (or npx next start) |
| Nuxt | node .output/server/index.mjs |
| NestJS | npm run start:prod or node dist/main |
| Express, Fastify, Koa, Hono | npm run start, or node <main> / server.js / index.js / app.js |
| Django | .venv/bin/gunicorn <project>.wsgi:application --bind 127.0.0.1:$PORT |
| FastAPI | .venv/bin/uvicorn main:app --host 127.0.0.1 --port $PORT |
| Flask | .venv/bin/gunicorn app:app --bind 127.0.0.1:$PORT |
| Go | ./bin/<module name> (built with go build -o bin/…) |
| Any project with a Procfile | the web: line |
For Python, the install command creates a virtual environment in .venv and installs requirements.txt, pyproject.toml or Pipfile; gunicorn or uvicorn is added when it is missing. For Django, add your domain to ALLOWED_HOSTS (or read it from an environment variable).
Put secrets and settings in Environment variables. They are available during the build and at runtime, and members with view-only access cannot see their values.
Zero-downtime (blue/green) releases
Every server app has two slots. A new release:
- is built in its own release folder;
- starts in the idle slot, on that slot's port;
- must answer on the Health check path (default
/) with a status below 500 within 90 seconds; - only then receives traffic: nginx is reloaded gracefully to the new slot, and the old slot is stopped.
If the new release fails to build, exits during startup or never becomes healthy, the running release is not touched. Rollback in the Releases card (the last 5 builds are kept) uses the same mechanism.
Because two releases briefly run side by side, keep state that must survive a release in the database, Redis or a Persistent path (for example uploads or .env), not inside the release folder.
Use every CPU core (Node.js)
Node.js runs on a single core. For Node.js server apps, the Use every CPU core card can run one process per CPU of the package without code changes, which serves several times more requests. Turn on One process per CPU core (redeploys to apply) only if the app keeps no state in memory: keep sessions, cache and websockets in Redis or a database.
Start, stop and restart
The status card on the Deploy tab shows the app's state, framework, runtime and internal port, with buttons to Deploy now, Restart, stop and start the app. The service restarts automatically if the process exits.
Resource usage counts toward the account's package: memory, CPU and processes of all websites and apps are shown under Resources.
Logs
- Application output on the Deploy tab shows the latest 400 lines written by the app (stdout and stderr, both slots). Turn on Live to follow it.
- The build log of every deployment is in Tasks.
- The Logs tab shows nginx's access and error logs. A 502 error there usually means the app is not listening on
$PORT; Run diagnostics detects this and suggests a fix.
Troubleshooting
| Symptom | What to check |
|---|---|
| "The app did not respond on port … within 90s" | The app must listen on 127.0.0.1:$PORT, not a fixed port. |
| "The app exited during startup" | Read Application output: usually a missing environment variable or dependency. |
| "Node.js is not installed — install it under Runtimes" | Ask the administrator to install a Node.js version. |
| Health check returns 404 or a redirect | Any status below 500 passes. A 5xx means the app is broken; set Health check path to a route that answers quickly. |