QGIS reads whatever path its data providers can reach, so a VPN that is up is only half of the setup. The tunnel hands the desktop a route and a resolver policy; the project still needs the service endpoint, the provider protocol and credentials the service will accept. Those are three separate entries, and typing the VPN's public listener into a database field is none of them. This walkthrough follows one PostGIS layer from the QGIS form to a returned row and names the boundary each step proves.

Start from the layer, not from the VPN

Each layer type reaches a different kind of endpoint. A PostGIS layer opens a PostgreSQL connection with a host, port, database and role. A WMS or WFS layer fetches a service URL and then parses a capabilities document. A shapefile on a network share only needs a file path that the operating system can open. Because those are three different mechanisms, an empty project tells you nothing until you know which one you are debugging.

Ask the GIS operator for the endpoint, the port, the required identity and the access policy that applies to your account. Keep the service hostname exactly as issued when the deployment depends on private DNS or on certificate name checks. Reusing a host address copied from another user's saved connection is the fastest way to reach a machine that answers but holds an older database.

Routes, allowed resources and name resolution belong to whoever runs the VPN. A data-source form cannot install a route that the tunnel profile never pushed, and it cannot widen a gateway policy. When the host resolves and the port answers, the network side of the problem is finished.

Enter the private endpoint the operator issued

The PostgreSQL form in QGIS mirrors the fields libpq expects, so fill it in the same order a command-line connection would. Service and host take the database machine, port takes the listener the server actually opens, and database takes the specific catalog rather than the server default. Store the password in an authentication configuration instead of the project file, so a shared project does not carry a credential.

Separate the two names before you type anything. The gateway name belongs in the VPN client and the database name belongs in QGIS, and they are rarely the same host.

VPN gateway:   vpn.gis.lab.example       (profile only, not a data source)
Database host: db.gis.lab.example:5432   (PostGIS service)
Database name: gis_prod
TLS mode:      verify-full

If the database sits on another machine behind the gateway, the name in the form must be that machine. Private DNS through the tunnel is what makes the name work, and a project that resolves only while connected is behaving correctly rather than caching badly.

Keep the database TLS mode separate from the tunnel

The SSL in a VPN product name describes the tunnel, not the database session. QGIS passes the PostgreSQL SSL mode through to libpq, and that mode controls whether the client verifies the server certificate and whether it checks the hostname inside it. verify-full does both, which is why it is the mode worth keeping when the certificate carries the service name.

Turning the mode down to disable because the tunnel is already encrypted removes the check that the server you reached is the server you asked for. When verification fails, the usual cause is a name mismatch or a missing root certificate, and both are fixable without weakening the connection. Point sslrootcert at the CA the operator publishes and keep the hostname in the form identical to the name in the certificate.

A tunnel with a private route and a database with a valid certificate can still fail on the role. Application credentials are separate from VPN credentials, and an account that authenticates to the gateway has no implied rights in PostgreSQL.

Test each boundary in order and read the first failure

Work outward from the client in a fixed order: name resolution, TCP reachability, TLS, authentication, then the query itself. The first step that fails is the only one worth reporting, and skipping ahead produces error messages from a boundary you have not reached yet. Four commands cover the first four steps.

getent hosts db.gis.lab.example
nc -vz db.gis.lab.example 5432
psql "host=db.gis.lab.example port=5432 dbname=gis_prod sslmode=verify-full" -c 'select 1'
curl -sS 'https://wms.gis.lab.example/ows?service=WMS&request=GetCapabilities' | head -c 200

An empty answer from getent is a resolution problem, not a database problem. A refused or timed-out nc means the route or a firewall rule is missing, which is a gateway-side fix. A TLS error from psql means the certificate or the name is wrong, while password authentication failed means you reached PostgreSQL and the credential is the issue. For a WMS layer, the curl call returns the capabilities XML that QGIS would parse, so an HTML login page in that output explains an empty layer immediately.

A PostgreSQL error is good news compared with a timeout. It proves the destination answered, which moves the ticket from the network team to the database or GIS administrator. Keeping that distinction in the report saves a round trip.

When the project opens with empty layers

Inside QGIS, check the layer's stored source before re-entering anything. The layer properties show the exact connection string the project saved, and a duplicate connection entry with an older host is a common leftover from a previous office network. Reloading the project after fixing a connection does not always re-read the provider, so remove the layer and add it again from the corrected connection.

Then confirm the authentication configuration is the one attached to the layer, not merely present in the list. A layer that was saved with a different auth config fails silently in the browser panel while the standalone psql test passes.

When the layer loads, save the project with relative paths only for files and keep service connections named after their endpoint. The next person who inherits the project can then tell which host a broken layer expected without opening a ticket.

Artihkkalat mat laktásit