1
0
mirror of synced 2026-10-23 07:08:55 +03:00

Add Caddy reverse proxy configuration examples (#10344)

Adds Caddy examples alongside the existing NGINX ones for hosting pgAdmin with
Gunicorn or uWSGI, both at the root of a server and under a sub-directory,
mirroring the structure of the NGINX sections. Caddy does not speak the native
uwsgi protocol, so its uWSGI examples run uWSGI in HTTP mode instead.

Documents the two things that must line up for a sub-directory deployment
behind any reverse proxy: the proxy forwarding the request path unmodified
with an X-Script-Name header, and a SCRIPT_NAME environment variable on the
pgAdmin process itself, which is read at startup to set APPLICATION_ROOT and
SESSION_COOKIE_PATH. The missing environment variable is what caused the
"login succeeds then bounces back to the login page" report in #10334.

Also notes that pgAdmin trusts X-Script-Name and X-Scheme unconditionally,
unlike the X-Forwarded-* headers, so every example now either overwrites or
clears both rather than passing on whatever the client sent; warns that the
systemd unit shipped with Caddy sets PrivateTmp=true, which hides a socket
created in the system /tmp; and refreshes the stale python3.7 paths.
This commit is contained in:
Dave Page
2026-09-22 17:03:47 +01:00
parent afa0c773ad
commit 2ba68de777

View File

@@ -195,7 +195,7 @@ the Python Wheel (you may need to adjust the path to suit your installation):
gunicorn --bind 0.0.0.0:80 \
--workers=1 \
--threads=25 \
--chdir /usr/lib/python3.7/dist-packages/pgadmin4 \
--chdir /usr/lib/python3.13/dist-packages/pgadmin4 \
pgAdmin4:app
Standalone uWSGI Configuration
@@ -210,9 +210,48 @@ the Python Wheel (you may need to adjust the path to suit your installation):
uwsgi --http-socket 0.0.0.0:80 \
--processes 1 \
--threads 25 \
--chdir /usr/lib/python3.7/dist-packages/pgadmin4/ \
--chdir /usr/lib/python3.13/dist-packages/pgadmin4/ \
--mount /=pgAdmin4:app
.. _sub_directory_hosting:
Hosting in a Sub-Directory
--------------------------
Any of the reverse proxy configurations described below can host pgAdmin in a
sub-directory of the server (``/pgadmin4`` in each of the examples) rather than
at its root, but doing so requires that the ``SCRIPT_NAME`` environment
variable is set for the Gunicorn or uWSGI process, whichever is in use.
pgAdmin reads ``SCRIPT_NAME`` from the process environment once, at startup,
and uses it to set ``APPLICATION_ROOT`` and ``SESSION_COOKIE_PATH``, so that
URLs, redirects (including the one issued after a successful login) and
session cookies are all generated relative to the sub-directory rather than to
the root of the server. Without it, a successful login redirects to the root of
the server, which typically presents to the user as a redirect loop.
This is a separate mechanism from the per-request ``X-Script-Name`` header that
the NGINX Gunicorn example below sets, and neither substitutes for the other.
It is also separate from uWSGI's ``--manage-script-name``, which only affects
the per-request WSGI environment that uWSGI builds; the uWSGI examples below
set ``SCRIPT_NAME`` alongside it so that both mechanisms agree, and so that the
session cookie is confined to the sub-directory rather than being issued for
the root of the server.
.. _proxy_headers:
Reverse Proxy Headers
---------------------
pgAdmin honours the ``X-Script-Name`` and ``X-Scheme`` request headers
unconditionally, unlike the ``X-Forwarded-*`` headers, which are trusted only
to the depth configured by the ``PROXY_X_*_COUNT`` settings. A client that
supplied its own ``X-Script-Name`` could therefore change every URL pgAdmin
generates, so the reverse proxy must always either overwrite both headers with
values of its own or remove them outright, rather than passing on whatever the
client sent. Each of the examples below does one or the other, and a proxy
configuration not derived from them should do the same.
NGINX Configuration with Gunicorn
---------------------------------
@@ -228,7 +267,7 @@ command similar to:
gunicorn --bind unix:/tmp/pgadmin4.sock \
--workers=1 \
--threads=25 \
--chdir /usr/lib/python3.7/dist-packages/pgadmin4 \
--chdir /usr/lib/python3.13/dist-packages/pgadmin4 \
pgAdmin4:app
And configure NGINX:
@@ -238,11 +277,24 @@ And configure NGINX:
location / {
include proxy_params;
proxy_pass http://unix:/tmp/pgadmin4.sock;
proxy_set_header X-Script-Name "";
proxy_set_header X-Scheme "";
}
Alternatively, pgAdmin can be hosted in a sub-directory (/pgadmin4 in this case)
on the server. Start Gunicorn as when using the root directory, but configure
NGINX as follows:
on the server. Start Gunicorn as when using the root directory, but also set the
``SCRIPT_NAME`` environment variable for the Gunicorn process, as described in
:ref:`sub_directory_hosting`:
.. code-block:: bash
SCRIPT_NAME=/pgadmin4 gunicorn --bind unix:/tmp/pgadmin4.sock \
--workers=1 \
--threads=25 \
--chdir /usr/lib/python3.13/dist-packages/pgadmin4 \
pgAdmin4:app
Then configure NGINX:
.. code-block:: nginx
@@ -250,6 +302,7 @@ NGINX as follows:
include proxy_params;
proxy_pass http://unix:/tmp/pgadmin4.sock;
proxy_set_header X-Script-Name /pgadmin4;
proxy_set_header X-Scheme "";
}
NGINX Configuration with uWSGI
@@ -267,7 +320,7 @@ command similar to:
uwsgi --socket /tmp/pgadmin4.sock \
--processes 1 \
--threads 25 \
--chdir /usr/lib/python3.7/dist-packages/pgadmin4/ \
--chdir /usr/lib/python3.13/dist-packages/pgadmin4/ \
--manage-script-name \
--mount /=pgAdmin4:app
@@ -279,20 +332,23 @@ And configure NGINX:
location @pgadmin4 {
include uwsgi_params;
uwsgi_pass unix:/tmp/pgadmin4.sock;
uwsgi_param HTTP_X_SCRIPT_NAME "";
uwsgi_param HTTP_X_SCHEME "";
}
Alternatively, pgAdmin can be hosted in a sub-directory (/pgadmin4 in this case)
on the server. Start uWSGI, noting that the directory name is specified in the
``mount`` parameter:
``mount`` parameter, and that the ``SCRIPT_NAME`` environment variable should
be set alongside it, as described in :ref:`sub_directory_hosting`:
.. code-block:: bash
uwsgi --socket /tmp/pgadmin4.sock \
--processes 1 \
--threads 25 \
--chdir /usr/lib/python3.7/dist-packages/pgadmin4/ \
--manage-script-name \
--mount /pgadmin4=pgAdmin4:app
SCRIPT_NAME=/pgadmin4 uwsgi --socket /tmp/pgadmin4.sock \
--processes 1 \
--threads 25 \
--chdir /usr/lib/python3.13/dist-packages/pgadmin4/ \
--manage-script-name \
--mount /pgadmin4=pgAdmin4:app
Then, configure NGINX:
@@ -303,6 +359,161 @@ Then, configure NGINX:
location @pgadmin4 {
include uwsgi_params;
uwsgi_pass unix:/tmp/pgadmin4.sock;
uwsgi_param HTTP_X_SCRIPT_NAME "";
uwsgi_param HTTP_X_SCHEME "";
}
Caddy Configuration with Gunicorn
----------------------------------
pgAdmin can be hosted by Gunicorn, with Caddy in front of it as a reverse
proxy. Note that these examples assume pgAdmin was installed using the Python
Wheel (you may need to adjust the path to suit your installation).
To run with pgAdmin in the root directory of the server, start Gunicorn using
a command similar to:
.. code-block:: bash
gunicorn --bind unix:/run/pgadmin4/pgadmin4.sock \
--workers=1 \
--threads=25 \
--chdir /usr/lib/python3.13/dist-packages/pgadmin4 \
pgAdmin4:app
And configure Caddy:
.. code-block:: text
pgadmin.example.com {
reverse_proxy unix//run/pgadmin4/pgadmin4.sock {
header_up -X-Script-Name
header_up -X-Scheme
}
}
.. _caddy_socket_location:
.. note:: The examples deliberately avoid ``/tmp`` for the socket. The systemd
unit shipped with Caddy sets ``PrivateTmp=true``, which gives the Caddy
process a private ``/tmp`` directory of its own, so a socket created by
Gunicorn or uWSGI in the system ``/tmp`` is invisible to Caddy, which will
report a 502 error for every request. Any directory outside ``/tmp`` that
both processes can access will do, provided the user Caddy runs as has
permission to read and write the socket; alternatively, have Gunicorn or
uWSGI listen on a TCP port such as ``127.0.0.1:5050`` and proxy to that
instead. Whichever directory is used, it must exist and be writable by the
user Gunicorn or uWSGI runs as before the socket can be bound; under
systemd, ``RuntimeDirectory=pgadmin4`` in the unit that starts Gunicorn or
uWSGI will create ``/run/pgadmin4`` on start and remove it on stop. The
same applies to the uWSGI examples below.
.. _caddy_header_stripping:
.. note:: The two ``header_up`` directives remove any ``X-Script-Name`` and
``X-Scheme`` headers that arrive from the client, for the reasons given in
:ref:`proxy_headers`. Each of the Caddy examples removes both, except that
the Gunicorn sub-directory example sets ``X-Script-Name`` to the
sub-directory instead, which likewise replaces whatever the client sent.
Alternatively, pgAdmin can be hosted in a sub-directory (/pgadmin4 in this
case) on the server. Start Gunicorn as when using the root directory, but
also set the ``SCRIPT_NAME`` environment variable for the Gunicorn process, as
described in :ref:`sub_directory_hosting`:
.. code-block:: bash
SCRIPT_NAME=/pgadmin4 gunicorn --bind unix:/run/pgadmin4/pgadmin4.sock \
--workers=1 \
--threads=25 \
--chdir /usr/lib/python3.13/dist-packages/pgadmin4 \
pgAdmin4:app
Then configure Caddy. The request path must be passed through to Gunicorn
unchanged, so do not strip the /pgadmin4 prefix; the ``X-Script-Name`` header
is set to the sub-directory, both to tell pgAdmin which requests are made
under it and to replace any value the client may have sent; and the path
matcher must include both ``/pgadmin4`` and ``/pgadmin4/*``, so that pgAdmin's
own redirect to the bare sub-directory path (with no trailing slash) is also
proxied, rather than falling through to Caddy's default (and rather unhelpful)
empty response for an unmatched path:
.. code-block:: text
pgadmin.example.com {
@pgadmin path /pgadmin4 /pgadmin4/*
handle @pgadmin {
reverse_proxy unix//run/pgadmin4/pgadmin4.sock {
header_up X-Script-Name /pgadmin4
header_up -X-Scheme
}
}
}
Caddy Configuration with uWSGI
-------------------------------
Caddy does not speak the native uwsgi protocol used by NGINX's
``uwsgi_pass``, so uWSGI must be run in HTTP mode instead, using
``--http-socket`` rather than ``--socket``. Note that these examples assume
pgAdmin was installed using the Python Wheel (you may need to adjust the
path to suit your installation).
To run with pgAdmin in the root directory of the server, start uWSGI using a
command similar to:
.. code-block:: bash
uwsgi --http-socket /run/pgadmin4/pgadmin4.sock \
--processes 1 \
--threads 25 \
--chdir /usr/lib/python3.13/dist-packages/pgadmin4/ \
--mount /=pgAdmin4:app
And configure Caddy, again keeping the socket out of ``/tmp`` (see
:ref:`the note above <caddy_socket_location>`) and stripping the headers
pgAdmin trusts unconditionally (see :ref:`the note above
<caddy_header_stripping>`):
.. code-block:: text
pgadmin.example.com {
reverse_proxy unix//run/pgadmin4/pgadmin4.sock {
header_up -X-Script-Name
header_up -X-Scheme
}
}
Alternatively, pgAdmin can be hosted in a sub-directory (/pgadmin4 in this
case) on the server. Start uWSGI, noting that the directory name is specified
in the ``mount`` parameter, and that the ``SCRIPT_NAME`` environment variable
should still be set alongside it, as described in
:ref:`sub_directory_hosting`:
.. code-block:: bash
SCRIPT_NAME=/pgadmin4 uwsgi --http-socket /run/pgadmin4/pgadmin4.sock \
--processes 1 \
--threads 25 \
--chdir /usr/lib/python3.13/dist-packages/pgadmin4/ \
--manage-script-name \
--mount /pgadmin4=pgAdmin4:app
Then configure Caddy as for the Gunicorn sub-directory example above, except
that no ``X-Script-Name`` header needs to be set: ``--manage-script-name`` has
uWSGI report the mount point to pgAdmin with every request, as in the NGINX
uWSGI example above, so the header is only removed:
.. code-block:: text
pgadmin.example.com {
@pgadmin path /pgadmin4 /pgadmin4/*
handle @pgadmin {
reverse_proxy unix//run/pgadmin4/pgadmin4.sock {
header_up -X-Script-Name
header_up -X-Scheme
}
}
}
Additional Information