# Python apps, Java apps and reverse proxies

> Source: https://www.allsweb.com/sixpanel/docs/python-java-proxy
> Markdown for agents: https://www.allsweb.com/sixpanel/docs/python-java-proxy.md
> Publisher: AllsWeb (www.allsweb.com)

Part of: SixPanel documentation

**What this page is for:** three more kinds of website beside the static website, the
Node.js app, the PHP website and WordPress:

- a **Python app** — Django, Flask, FastAPI, or any WSGI or ASGI app;
- a **Java app** — Spring Boot, Quarkus, Micronaut, or anything that runs from a JAR;
- a **reverse proxy** — your domain, with HTTPS, in front of an app that runs somewhere
  else: on another port of this server, or on another server.

Each is a project in the **Projects** list with its own menu, its own Linux account and
its own folder, exactly like the other websites ([Websites and apps](https://www.allsweb.com/sixpanel/docs/websites-and-apps)).
Nothing about your 6amMart projects or your other websites changes when you add one.

**You need**

- For a Python or Java app: its code — a folder, a ZIP or a git repository. A Java app
  can also be uploaded as a ready `.jar`.
- For a reverse proxy: the address it sends visitors to, like `http://127.0.0.1:3000`
  or `https://app.example.com`.
- A web address for it (optional when you create it — add it later on **Domain & SSL**).
- Nothing to install first. What a Python or Java app needs is installed by itself the
  first time it is needed (see [Software](#software-python-and-java)).

---

## Create one

**Projects → Create a new project**, then **Python app**, **Java app** or **Reverse
proxy**. A Python or Java app asks for a name, an optional web address and whether it
needs a MariaDB database. A reverse proxy asks for a name, the optional web address,
and two things of its own:

- **Send visitors to** — the address of the app behind it.
- **Host header** — the name the app behind is told it was asked for (see
  [Host header](#host-header)). **Automatic** is right almost always.

A Python or Java app opens on **Deploys** once it is made; a reverse proxy opens on
**Domain & SSL**, because its address is all it still needs.

## Python app

1. **Deploys** — drop the folder or ZIP on the page, or connect git.
2. The panel reads the project and fills in how it runs:
   - **Django** (a `manage.py`): gunicorn, or uvicorn when the project uses Channels
     or Daphne and has an `asgi.py`; the app module
     (`mysite.wsgi:application`); `python manage.py migrate --noinput` as a deploy
     command; `db.sqlite3` and `media/` kept between deploys.
   - **Flask**: gunicorn, with the app object or the `create_app()` factory it finds;
     `instance/` kept between deploys.
   - **FastAPI**, **Starlette**, **Quart**, **Litestar** and **Sanic**: uvicorn, with
     the app object it finds.
   - Anything else: the `wsgi.py` / `asgi.py` it finds, or `python app.py` (or
     `main.py`, `server.py`, `run.py`) as the start command.
3. Every deploy makes a **virtual environment of its own** for the new version
   (`.venv` inside it) and installs `requirements.txt` — or the project itself when
   there is a `pyproject.toml` — with the server's own Python. gunicorn or uvicorn is
   added when the project does not list it.
4. **Django**: `collectstatic` runs on every deploy, and nginx serves `STATIC_URL` and
   `MEDIA_URL` itself — your app is never asked for a picture. This needs
   `STATIC_ROOT` in `settings.py` (`STATIC_ROOT = BASE_DIR / "staticfiles"`); the deploy
   log says so when it is missing.
5. The new version starts beside the live one, must answer on the **Health check
   path**, and only then do visitors move over. A version that does not start never
   goes live; the last three versions stay for a one-click rollback.

**What your app is given** (in its environment, also for its deploy commands and its
scheduled tasks):

- `PORT` and `HOST` — the panel's. gunicorn and uvicorn are started on `127.0.0.1` and
  that port for you. Your own start command must listen there too: a version that
  listens on any other address is refused and never goes live.
- `SIXPANEL_SITE_HOSTS` — this website's addresses, comma-separated, and
  `SIXPANEL_SITE_URL` — its main one with `https://`.
- With a database: `DATABASE_URL`, `DB_HOST`, `DB_PORT`, `DB_DATABASE`, `DB_USERNAME`,
  `DB_PASSWORD` and `DB_SOCKET`. Nothing to copy.
- Your own variables from **Environment** (any `.env` in your files is imported there).

**Django behind the panel** — three lines in `settings.py` make it safe and correct
behind nginx and HTTPS:

```python
import os
ALLOWED_HOSTS = os.environ.get("SIXPANEL_SITE_HOSTS", "localhost").split(",")
CSRF_TRUSTED_ORIGINS = ["https://" + h for h in ALLOWED_HOSTS]
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
```

**Kept between deploys** (Settings): files and folders the app writes that a new
version must not lose — uploads, a SQLite file. They live in the website's own `data/`
folder; each version links to them, and backups carry them. A folder ends in `/`.

**Python versions.** The server's own Python is the one its distribution ships (Ubuntu
26.04: 3.14, Ubuntu 24.04: 3.12, Debian 13: 3.13), and it is the default. Other CPython
versions (3.11, 3.12, 3.13, 3.15 …) can be installed beside it on **Software →
Python** — see below. A website builds with:

- the version chosen in **Settings → Python version** (the server's own, or an installed
  one), or
- with **Match the project** (the default), the version its project asks for
  (`requires-python` in `pyproject.toml`, or `.python-version`): the server's own when it
  fits, else the newest installed one that does. When nothing installed fits, the deploy
  log says so and names the version to install, and the build uses the server's own.

The version live now is the one its virtual environment was made with: choosing another
or installing a new one moves nothing that is running — the next deploy builds with it,
and a rollback runs on the version that release was built with. After the server's
operating system is upgraded to a new release, **deploy once every Python website that
uses the server's own Python**: its virtual environment was made with the old one.

**Packages without a ready wheel** (`mysqlclient` and a few others) need the build
tools: **Software → Python → Build tools → Install** (about 250 MB), then
deploy again. `PyMySQL` needs nothing.

## Java app

1. **Deploys** — drop the source folder or ZIP, connect git, or drop a ready `.jar`.
2. The panel reads the project:
   - `pom.xml`: built with the project's `./mvnw`, or with Maven when it has none
     (`-DskipTests package`).
   - `build.gradle` / `build.gradle.kts`: built with the project's `./gradlew`
     (`bootJar` for Spring Boot). A Gradle project without its wrapper is refused with
     the reason — the distribution's own Gradle is too old.
   - plain `.java` files: compiled with `javac`; the class with a `main` method is run.
   - a ready `.jar`: run as it is. It must name its `Main-Class` (every Spring Boot JAR
     does); a library JAR is refused at once.
3. It runs with `java -Xmx… -jar …` as the website's own account. The heap is 70% of
   the website's **memory** setting; the rest is the JVM's own.
4. The new version must answer within the **start time allowed** — 180 seconds unless
   you change it, because a Java app is slow to start — and only then do visitors move
   over.

**Java versions.** The project says which Java it needs (`maven.compiler.release`,
`java.version`, a Gradle toolchain). The build uses the oldest installed long-term
version that is at least that — and when none is installed, the deploy **installs one
by itself** from the distribution. **Settings → Java version** chooses one for the next
build instead. The version live now is the one it was built with: installing a newer
Java never moves a running app, and a rollback runs on the version that release was
built with.

**What your app is given**: Spring Boot, Quarkus and Micronaut are told `127.0.0.1` and
the port by themselves. Any other server must listen on `HOST` (127.0.0.1) and `PORT`.
With a database: `SPRING_DATASOURCE_URL`, `SPRING_DATASOURCE_USERNAME`,
`SPRING_DATASOURCE_PASSWORD` and the same three as `JDBC_DATABASE_URL`,
`JDBC_DATABASE_USERNAME`, `JDBC_DATABASE_PASSWORD` — a Spring Boot app needs no
configuration for it. `SIXPANEL_SITE_HOSTS` and your own variables as above.

**Settings** has the build command, the JAR to run (`target/app.jar`), **JVM options**
(`-Dspring.profiles.active=prod` — not `-Xmx`, which follows the memory setting),
program arguments, deploy commands (a database migration) and the paths kept between
deploys.

## Reverse proxy

A reverse proxy runs nothing on this server: nginx answers on your domain with HTTPS and
sends every request on to the address you gave. It has no files, no database and no
deploys — its page is **Reverse proxy**.

### Where visitors go

**Send visitors to** — `http://` or `https://`, a name or an IP address, a port, and
optionally a path (`https://app.example.com/base`); the visitor's own path and query
follow it. Some addresses are refused, with the reason:

- the cloud's metadata address (`169.254.169.254` and the names that lead to it — it
  hands out this server's own cloud credentials);
- SixPanel's own port, and nginx's own ports 80 and 443 on this server (visitors would
  go round in a circle);
- ports that are not web apps (SSH, mail, DNS);
- this website's own domain.

A **name** is looked up by the panel, every answer is checked against the same list,
and nginx is given the addresses — never the name. The name is looked up again every
five minutes; a lookup that fails keeps the addresses it had, and one that suddenly
answers a refused address takes them away (the website then answers 502 rather than
send anybody there). **Look the name up again** does it at once.

**A port of another website on this server** (a Node.js, Python or Java app's slot) is
followed: those apps move between two ports at every deploy, and the proxy moves with
the live one by itself.

**Test the target** sends one request the way the website sends it, and says the HTTP
status, how long it took and whether the certificate checked out.

### Host header

- **Automatic** — the visitor's domain for an IP address (an app on this server),
  the target's own name for a name (another server, which usually serves several sites
  and needs its own name).
- **The visitor's domain**, **the target's own name**, or **a name you give**.

### How it connects

Websockets (on by default), the connect and answer timeouts, the largest upload,
buffering (switch it off for a live stream or server-sent events), and — for an
`https://` target — whether its certificate is checked (automatic: checked for a name,
not for an IP address) and whether the server name is sent (SNI). **Refuse requests
for .env, .git and similar files** stops those before they reach the target.

The app behind receives `X-Forwarded-For`, `X-Forwarded-Proto: https`,
`X-Forwarded-Host` and `X-Real-IP`.

### Extra request headers

Headers sent to the target with every request — an API key the target expects, for
example. A header whose name says it is a key, a token, a password or a cookie is kept
**secret** (you can tick or untick it): its value is stored readable by root only, is
never shown again — here, in the API or to Tia — and is replaced by typing a new one.

### Login

**Ask every visitor for a user name and password** puts a browser login (basic auth) in
front of the target. Add users below it; a password is stored only as a hash. With the
login on and no user yet, every visitor is refused — it never opens by mistake. The
password a visitor types for this login is not passed on to the target.

### Short cache

Keeps pages the target allows to be cached for a few seconds and shares them between
visitors. A visitor with a cookie or a login always gets a fresh answer, and nothing
is stored from one. **Overview → Empty the cache** empties it.

### Web Rules

Redirects, rewrites and response headers work on a reverse proxy as on every website
(**Web Rules**, in its menu), and on Python and Java apps too. A **Rewrite** on a proxy
whose target has a path is sent on after that path: `/old` served as `/new` reaches
`https://app.example.com/base/new`. The cost, on such a website only: an encoded slash
(`%2F`) in an address reaches the target as a plain `/`. A target path that itself holds
an encoded character (`/my%20app`) is always sent addresses exactly as visitors typed
them, so a Rewrite does not reach it — use a redirect there.

### What it does not have

No **Deploys**, **Database**, **Environment** or **Cron jobs** — nothing runs
here. Nothing of it is in a backup of its own: its settings, users and secret headers
are part of the panel's data, which every backup carries. A staging site is not
offered: to try another target, add a second reverse proxy.

## Software → Python and Java

- **Python** — the server's own Python, the virtual-environment module every Python
  website needs (installed by itself the first time one is created), and the optional
  build tools. Removing is offered only for what the panel installed; Python itself is
  the system's and stays.
  **Versions** lists the server's own and every other version installed, with the
  websites on each, and the versions that can be added. They come from
  python-build-standalone (the builds `uv` uses), each download checked against that
  release's published SHA-256 before anything is unpacked — a download that does not
  match installs nothing — into the panel's runtimes folder (about 100 MB each), on both
  amd64 and arm64 servers. **Remove** is offered only for a version no website builds or
  runs on.
- **Updates** (on Python's and Java's pages, and on Software → Node.js) — each version
  installed for websites, and whether a newer patch of it exists. **Check for updates**
  asks upstream; **Update** installs it and restarts the running websites on it (a second
  or two each); **Hold** keeps a version where it is; **Skip this release** passes over
  one release until a newer one comes. A Python patch (3.13.15 → 3.13.16) replaces the
  version's folder in place, so every website's virtual environment keeps working with
  no rebuild; if a website does not start on it, the old build is put back. A Java
  update is the distribution's newer package of that version. The server's own Python
  follows the distribution's daily security updates and is not updated here.
- **Java** — the long-term OpenJDK versions this server's distribution offers, which
  are installed, and which websites build or run on each; **Install** adds one ahead of
  time, **Remove** takes away one that nothing uses. **Maven** is installed by itself
  the first time a project with a `pom.xml` and no `./mvnw` needs it.

Only the owner's login installs or removes software.

## What Tia can do

Tia creates all three kinds (*"put my app on port 3000 behind shop.example.com"* is a
reverse proxy with its domain and HTTPS in one go), changes a reverse proxy's settings,
tests it, installs or removes Python's build tools, Python versions and Java versions, and checks for and installs their updates. Making a
reverse proxy, pointing one somewhere new, adding a secret header or a login user and
turning its login off always wait for your approval. A password or a secret header value is never typed to her: she asks for it
with a secure field, and she only ever sees `****`.

## How to check it worked

- **Overview** — *Is it live?* reads **Yes**, the address answers over HTTPS, and a
  Python or Java app's chip says the Python or the Java it runs on.
- **Deploys** — the version live now, and its log: what was detected, installed and
  built.
- **Reverse proxy → Test the target** answers with the HTTP status.
- **Logs → App output** — what the app printed; a Python traceback or a Java exception
  is counted on the Overview's *Is anything failing?* answer.

## If it went wrong

- **"no way to start this app was found"** — set **Runs with** and the **App module**
  (or your own start command) in **Settings**, then **Build again**.
- **A Python package fails to build** — install the build tools on Software →
  Python, then deploy again.
- **The version never goes live and the log says it listens on another address** — your
  own start command listens on `0.0.0.0` or a fixed port: use `127.0.0.1` and `$PORT`.
- **A Java app is stopped before it answers** — raise **Start time allowed** in
  Settings, or give it more memory.
- **"this Gradle project has no Gradle wrapper"** — add it (`gradle wrapper`), commit it,
  deploy again.
- **A reverse proxy answers 502** — the target does not answer from this server: press
  **Test the target**. A name that does not resolve here, a firewall on the other
  server, or an app that is stopped are the usual reasons.
- **A reverse proxy answers 403 "has no users yet"** — its login is on: add a user, or
  turn the login off.
- **After an operating-system upgrade a Python website does not start** — deploy it
  once: its virtual environment is made again with the new Python.
