Reverse Proxy Routes
A reverse proxy route publishes a service running on your server under one of the
domains CorePanel already manages. Requests matching a path are forwarded to a
host:port you choose; every other path keeps being served from the domain’s document
root as usual.
https://app.example.com/api/orders ──▶ 127.0.0.1:8080 (your service)https://app.example.com/index.html ──▶ ~/public_html (the docroot)The service does not need a public port, a certificate, or a hostname of its own. It listens on the loopback interface, and the domain — with everything CorePanel already puts in front of it — becomes its front door.
What you get by proxying instead of exposing a port
Section titled “What you get by proxying instead of exposing a port”Running a service on :8080 and telling people to visit http://example.com:8080
works, but it is naked. Behind a CorePanel route, the same service gets:
- Automatic HTTPS. The domain’s certificate, issued and renewed by CorePanel. Your service keeps speaking plain HTTP on loopback.
- The WAF and the firewall. Requests are filtered before they reach your service, and the service’s own port never has to be open to the internet.
- Traffic statistics and logs. Proxied requests are counted on the dashboard’s Web Traffic panel like any other.
- A real hostname.
app.example.cominstead of an IP and a port number.
When to use it
Section titled “When to use it”Typical cases:
| Case | Route |
|---|---|
| An API next to a normal website | /api/* → 127.0.0.1:8080, docroot keeps serving the rest |
| A whole app (Node, Go, Python, Java) on its own domain or subdomain | /* → 127.0.0.1:3000 |
| A single-page app with a separate backend | SPA files in the docroot, /api/* → the backend with strip prefix |
| A service you installed yourself (Gitea, a metrics dashboard, a webhook receiver) | /* → its local port |
| A gradual rewrite | move one path at a time to the new service, leave everything else on the old site |
| A service on another machine in your private network | /* → 10.0.0.20:8080 |
Before you start: CorePanel does not run your service
Section titled “Before you start: CorePanel does not run your service”This is the one thing to be clear about.
The supported way to keep a service alive on a RHEL-family server is a systemd unit.
A minimal one, in /etc/systemd/system/myapp.service:
[Unit]Description=My applicationAfter=network.target
[Service]User=myaccountWorkingDirectory=/opt/userapps/myaccount/myappExecStart=/opt/userapps/myaccount/myapp/myappRestart=always
[Install]WantedBy=multi-user.targetsystemctl daemon-reloadsystemctl enable --now myappWith that in place the route is stable: systemd keeps the process up, CorePanel keeps the door open.
The shortcut: proxy a domain as you create it
Section titled “The shortcut: proxy a domain as you create it”Most addon domains and subdomains created for an application want the whole
hostname pointed at that application, so the Add domain and Add subdomain
dialogs offer it directly: switch on Serve from a local service, give the
host:port, and the site is published through your service the moment it exists.
That is exactly equivalent to adding a /* route afterwards, and the route shows
up in the Reverse proxy section like any other.
The option is not offered for a domain alias: an alias is served by the domain it points at and has no configuration of its own, so the route belongs on that domain instead.
If the domain is created but the route fails (a typo in the port, for instance), the domain is kept — you get a warning saying the route is still pending, and you add it from the section below.
Adding a route from the panel
Section titled “Adding a route from the panel”The editor lives in the account’s workspace, under Reverse proxy. It is administrator-only — see Why administrators only.
- Pick the site the route belongs to. Every primary domain, addon domain and subdomain of the account is listed. (Domain aliases are not: they have no configuration of their own and are served by the domain they point at, so the route goes on that domain.)
- Click Add route and fill in four fields:
- Path — what the route matches: a prefix (
/api/*), an exact path (/health) or/*for the whole site. - Upstream — where the requests go, as
host:port. Never a URL. - Strip the path prefix — off by default. See below.
- Connect over HTTPS — only if your service speaks TLS on that port.
- Path — what the route matches: a prefix (
- Use Test to dial the upstream before saving. It tells you whether anything is listening there right now.
- Save. The route applies immediately; no restart, no downtime for the rest of the site.
Once saved, each route shows whether its target is Responding (with the round-trip time) or gives No response and the reason. That indicator is the difference between “my site is broken” and “my service is down”, which from the outside are the same 502.
Path or prefix
Section titled “Path or prefix”The matcher is literal, and /api and /api/* are not the same thing:
| Matcher | Matches | Does not match |
|---|---|---|
/api/* | /api/, /api/orders, /api/v1/x | /api, /apidocs |
/api | /api exactly | /api/orders |
/* | everything | — |
If your service should own both /api and everything under it, add the two routes.
Strip the path prefix
Section titled “Strip the path prefix”By default the upstream receives the path exactly as the visitor typed it: a request to
/api/orders arrives at your service as /api/orders. With strip prefix on, the
matcher’s prefix is removed and the same request arrives as /orders.
Turn it on when your service is written as if it owned the root — which most frameworks
are, unless you configure a base path. Turn it off when the service already expects to
live under /api. It is ignored for a /* matcher, where there is nothing to strip.
Reading the route table
Section titled “Reading the route table”The panel lists the site’s whole route table in the order the web server evaluates it, not just the routes you added. The first match wins, so the order is what decides which route answers a request — hiding the rest would make a shadowed route look like a bug. CorePanel keeps proxy routes ordered most-specific-first, so a catch-all never swallows a more specific route.
Rows you cannot edit are shown for context:
- Automatic — maintained by CorePanel. The most common one is the docroot fallback: as soon as a site has an explicit route table, something has to keep serving the files, and that entry is it. It appears with your first proxy route and disappears with your last one.
- Manual — a route written by hand in the site’s configuration file. CorePanel displays it and leaves it alone.
Doing it from the command line
Section titled “Doing it from the command line”The same operations, from corepanel:
# See the whole table, in evaluation ordercorepanel domain proxy list app.example.com
# ...and dial each target while you are at itcorepanel domain proxy list app.example.com --check
# Publish a service under /api, which the service sees as /corepanel domain proxy add app.example.com '/api/*' 127.0.0.1:8080 --strip-prefix
# Put the whole domain in front of an appcorepanel domain proxy add app.example.com '/*' 127.0.0.1:3000
# Back to serving the docrootcorepanel domain proxy remove app.example.com '/api/*'add probes the upstream right after applying the route, so a typo in the port shows up
as a warning instead of as a 502 in someone’s browser. Quote the path: your shell would
otherwise expand the *.
Full flag reference in the CLI documentation.
Why administrators only
Section titled “Why administrators only”Reverse-proxy routes are restricted to super-administrators, and that is a security boundary rather than a policy about who is trusted with the UI.
Whoever creates a route chooses the upstream. Pointed at a service running on the server, a route publishes it on the public internet under the domain’s name — including services that are only safe because they listen on loopback. Handing this to an account would mean handing it the ability to expose the panel’s own API under its hostname.
CorePanel filters the obvious cases on top of that:
- The web server’s own ports (80 and 443) on loopback are refused outright: a site proxying to its own server is an endless request loop.
- Well-known local service ports (the CorePanel API, MySQL, PostgreSQL, Redis, Memcached,
SSH, SMTP) require an explicit confirmation, because the overwhelmingly likely
explanation for
127.0.0.1:3306in that field is a mistake.
Neither filter applies to remote hosts: proxying to 10.0.0.20:3306 is your network’s
business.
When something does not work
Section titled “When something does not work”A proxied path can only fail in a few ways, and they are easy to tell apart:
| Symptom | Where to look |
|---|---|
| 502 on the proxied path, docroot fine elsewhere | Your service is not listening. Check the route’s status indicator in the panel, or corepanel domain proxy list <domain> --check. |
| Your service will not start | systemctl status <unit> and journalctl -u <unit>. A 203/EXEC means systemd could not execute the binary — see the SELinux note above. |
| The service works from the shell but not under systemd | Almost always SELinux or a ProtectHome/ProtectSystem setting in the unit. |
| The upstream answers, but with the wrong paths | The prefix strip. /api/orders reaches your service as /orders with it on, /api/orders with it off. |
For anything SELinux-shaped, the audit log is /var/log/audit/audit.log:
ausearch -m AVC -ts recentgrep denied /var/log/audit/audit.log | tail # when ausearch comes back emptyRun both. ausearch filters by time and event type, and it will occasionally report
nothing for a denial that is sitting in the log file — so a plain grep on the raw log is
the check that settles it.
Limits and what comes next
Section titled “Limits and what comes next”- A route survives web-mode changes, optimization changes and certificate renewals. It does not survive deleting and recreating the domain.
- Up to 50 proxy routes per site.
- Removing a route never touches the service behind it. The path simply goes back to being served from the document root.
Proxy routes are the exposure half of running an application on a CorePanel server. The other half — CorePanel creating the systemd unit, keeping the process alive, applying resource limits, showing you its logs and deploying new versions — is the application runtime, and it is available: see Applications. Use a proxy route for a service you want to supervise yourself, and an application for one you would rather CorePanel supervised.
Upstreams on a unix socket
Section titled “Upstreams on a unix socket”You may also see an upstream that is not a host:port at all:
unix:/run/corepanel/apps/<account>/<app>.sockThat is how the application runtime publishes an application, and it is worth knowing why
before you meet one. A port on 127.0.0.1 is reachable by every process on the server,
another customer’s account included — so on a shared machine one account’s traffic could
reach another’s backend behind the web server, the WAF, and whatever authentication you put
in front. A socket file cannot be reached that way: it is owned by the account, its group is
the web server’s, its mode is 0660, and the directory above it is closed to everyone else.
The permissions are the isolation, and you can check them with ls.
Only that exact layout is accepted — one account directory, one .sock file. No other
socket on the server can be named, so the route editor can never be pointed at a system
service. In practice CorePanel writes these routes itself; you will see them in the table
rather than type them.
Your own service does not have to speak unix sockets to benefit from this: the runtime puts a small forwarder in front of it, so the application keeps listening on whatever port it already listens on, inside a network namespace where nothing else can reach it.