You open the layer in QGIS and it renders. A colleague on the same VPN gets an error for the same layer. Someone concludes the VPN is broken, and the next hour is spent moving a database or exposing a port that never needed either. The request from the desktop and the query GeoServer makes to PostGIS leave from different machines, in different network namespaces, under different credentials. Separating those two paths is most of the diagnosis.

Two connections, two origins

QGIS  ->  client's permitted path (often the VPN)  ->  GeoServer listener
GeoServer -> server's permitted path  ->  PostGIS listener

The second arrow does not inherit the first computer's tunnel. A GeoServer in a container has the container's network stack, not the host's, and a container on a bridged network can reach a database the host cannot, or the reverse. So the same user can succeed and fail within one session, depending on whether the answer came from cache or from a fresh query.

What you testedWhat it proves
Portal page or GetCapabilitiesHTTP reachability to GeoServer only
A tile served from cachenothing about the database
GetMap for an uncached layera datastore query had to happen

Find the step that actually failed

A service-level rejection, an image request that cannot reach its datastore, and a permission problem in one layer look similar in a browser tab and completely different in the server log. Ask for the specific operation, then read GeoServer's own event for the same timestamp.

curl -s -o /dev/null -w '%{http_code}\n' \
  "https://gis.example.com/geoserver/wms?service=WMS&version=1.3.0&request=GetCapabilities"

curl -s -o /tmp/tile.png -w '%{http_code} %{size_download}\n' \
  "https://gis.example.com/geoserver/wms?service=WMS&version=1.3.0&request=GetMap&layers=corp:parcels&bbox=-13650000,4500000,-13600000,4550000&width=256&height=256&srs=EPSG:3857&format=image/png"

A 200 from the second request with a plausible image proves the datastore answered. A 200 with a service exception document proves it did not, and the exception text usually names the store rather than the client.

Read the datastore from the server's side

The GeoServer PostGIS reference lists what the store needs: host, port, database and credentials. All of it describes the connection GeoServer opens. None of it describes a login by the desktop client. Read those four fields as a statement about the server's network context, and the whole class of "it works for them" tickets becomes tractable.

That is why localhost in a datastore is such a common mistake. It means the GeoServer process's own network context. If GeoServer runs in Docker while PostGIS runs on the host, localhost points inside the container and finds nothing, and no amount of VPN configuration changes that.

On the server, check the route, the listener and the authentication policy in that order. PostgreSQL's host-based authentication matches the source address it observes, which a gateway may translate, so adding a client VPN pool to the HBA file rarely fixes a server-side connection. Test as the server would: same host, same port, same user. If that test succeeds while GeoServer still fails, the difference is the process context rather than the database.

ip route get 10.20.5.11
ss -tnp | grep 5432
psql "host=10.20.5.11 dbname=gis user=geoserver" -c 'select 1'

Pools and caches hold state across changes

Datastore connection pools keep sockets open. A tunnel that came up an hour after the pool was created does not necessarily give the pool a new path, and an idle connection can sit on a dead route until validation or a timeout expires it. Check the installed store's validation settings instead of assuming recovery, and avoid the temptation to restart the whole service before you know which layer failed.

Tile caches create the mirror image of that problem: they can serve a correct-looking map long after the database connection broke. Clearing the cache to "see if it helps" destroys the one piece of evidence that would have told you which path was alive.

Hand off with both origins named

The description that gets a fast fix names two things: the client request that reached GeoServer, and the backend operation GeoServer could not complete. With both, the correct owner can repair the path that failed. With only the first, the natural fix is to expose the database publicly, which solves the ticket by creating a worse problem.

Note the layer, the response code, the server-side exception and the container or host that produced it. That is four lines, and it removes the ambiguity entirely.

ဆက်စပ်ဆောင်းပါးများ