# 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.

Source: https://zopanel.net/docs/apps-node-python  
Updated: 2026-10-07

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](/docs/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>/source` with 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:

```js
// Node.js (Express)
app.listen(process.env.PORT, "127.0.0.1");
```

```python
# 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:

1. is built in its own release folder;
2. starts in the idle slot, on that slot's port;
3. must answer on the **Health check path** (default `/`) with a status below 500 within 90 seconds;
4. 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. |
