2026-10-02 10:06:26.730 UTC [7207] [unknown]@[unknown]/[unknown] 10.42.1.5 LOG: connection received: host=10.42.1.5 port=60038
2026-10-02 10:06:26.742 UTC [7207] bench@postgres/[unknown] 10.42.1.5 LOG: connection authorized: user=bench database=postgres application_name=pgbench SSL enabled (protocol=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384, bits=256)
2026-10-02 10:06:26.742 UTC [7207] bench@postgres/pgbench 10.42.1.5 LOG: connection ready: setup total=13.170 ms, fork=0.513 ms, authentication=6.245 ms
%h, and the circled address is what %h prints: the IP address of
the client that connected, not of the server. PostgreSQL logged it without any DNS lookup.
Two settings that work together
The server log can show where each connection came from. Two settings decide how, and they do different jobs.
%his an escape inlog_line_prefix- the text PostgreSQL writes at the start of every log line. The documentation describes it as "Remote host name or IP address". On every line written for a client session, it prints where that client connected from.log_hostnameis an on/off setting, off by default. It decides what%hprints: with the setting off, the client's IP address; with it on, a host name that PostgreSQL looks up in DNS.
The PostgreSQL 18 documentation describes log_hostname like this: "By default, connection log messages only show the IP address of the connecting host. Turning this parameter on causes logging of the host name as well. Note that depending on your host name resolution setup this might impose a non-negligible performance penalty."
Two details matter in practice. First, log_hostname adds no field of its own. Without
%h - or %r, which adds the port - in log_line_prefix,
ordinary log lines do not show the client at all, whatever log_hostname says, and the
upstream default prefix, '%m [%p] ', has neither. The setting also changes the
host= field of the "connection received" message, which appears when connection
logging is on, and it fills the client_hostname column of
pg_stat_activity. Second, in our tests the host name replaced the IP address in
the log. It did not appear next to it.
What the lookup is, and when it happens
A reverse DNS lookup asks DNS which name belongs to an IP address (a PTR record). With log_hostname on, PostgreSQL makes this lookup for every new TCP connection, right after the server process for that connection starts - before the client's first message is read and before authentication. The connection waits until DNS answers, or until the system's resolver gives up. Connections over a local Unix socket make no lookup.
The lookup runs whenever the setting is on, also when nothing in your configuration prints
the name. Its time does not count against authentication_timeout, either. A
comment next to the lookup in the
PostgreSQL
18 source code says so directly: "PreAuthDelay and any DNS interactions above don't count
against the time limit."
What it cost with healthy DNS
We measured the cost on two virtual machines in Microsoft Azure, in one private network:
PostgreSQL 18.6 on Ubuntu 24.04 (Standard_D4s_v5, 4 vCPUs) and a separate client machine
(Standard_D2s_v5). Connections used TLS 1.3 and SCRAM-SHA-256 password authentication, as
many production setups do. The client ran pgbench, the benchmark tool that ships with
PostgreSQL, with a one-line custom script (SELECT 1) and the -C
option, which opens a new connection for every transaction. That makes connection setup
most of the work, which is what this test is about. It is not a throughput test of a real
workload.
Azure's DNS service answered the lookup for the client's address with the name
vm-logtalk-hostname-cli
For each setting, the table shows two numbers that pgbench prints at the end of a run.
Connection time is how long it took, on average, to open one connection. Connections per
second is how many times per second the client connected, ran SELECT 1 and
disconnected; pgbench reports it as transactions per second, and with -C every
transaction is one connection. With one client, each connection starts only after the
previous one has finished, so the two numbers move in opposite directions: when every
connection takes longer, fewer of them fit into a second. Connections per second here is
the pace of a single client connecting again and again, not the most the server could
accept.
| log_hostname | DNS lookups | Connection time (lower is better) |
Connections per second (higher is better) |
|---|---|---|---|
| off (the default) | none | 14.1 ms (14.1-14.2) | 67.8 (baseline) |
| on | through a local cache | 14.4 ms (14.3-14.5) | 66.6 (-1.8%) |
| on | straight to Azure DNS, no local cache | 20.2 ms (19.9-20.2) | 48.1 (-29%) |
Without a local cache, every new connection took about 6 ms longer: 43% more time per
connection, and 29% fewer connections per second in this test. The server's own timing
agrees. PostgreSQL 18 can log how long each connection took to set up
(log_connections with the setup_durations option): the average
grew from 10.4 ms to 16.3 ms, while the authentication part stayed at 6.0 ms in all three
variants. The extra time is spent before authentication, where the lookup happens.
With the local cache, the difference was 0.3 ms. Azure DNS gave this answer a lifetime (TTL) of 10 seconds, so most lookups came from the cache. The 6 ms is what one round trip to Azure DNS cost from this server. On your network it is a different number: the time your DNS server needs to answer.
What it cost with slow or failing DNS
Healthy DNS is the best case. We then slowed DNS down, and then made it unreachable - only for the database server and only for DNS traffic - and ran the same test with one client.
| DNS | log_hostname = off | log_hostname = on |
|---|---|---|
| answers 100 ms late | 14.0 ms | 122.6 ms |
| does not answer | 14.2 ms | 20,036 ms (20 seconds) |
| does not answer, local cache | not measured - no lookup | 10,027 ms (10 seconds) |
With log_hostname off, DNS problems made no difference, because there was no lookup. With it on, every new connection waited for DNS. When DNS answered 100 ms late, each connection took about 100 ms longer. When DNS did not answer, each connection waited until the resolver gave up: 20 seconds when the system resolver (glibc 2.39 on Ubuntu 24.04) asked DNS directly - it sent the question four times, five seconds apart - and 10 seconds through systemd-resolved. Resolver options can change these numbers, but not the pattern: a new connection takes as long as DNS takes to answer or to fail. With eight clients connecting at the same time and DNS down, the server completed 0.2 new connections per second. With log_hostname off and DNS healthy, the same eight clients completed 160 per second.
When the lookup fails, the IP address is lost too
These are three lines from one of those 20-second connections, with
%h in the log line prefix. The timestamp and process ID at the start of each
line are replaced with ..., and long lines are wrapped:
... [unknown]@[unknown]/[unknown] WARNING: pg_getnameinfo_all() failed:
Temporary failure in name resolution
... [unknown]@[unknown]/[unknown] ??? LOG: connection received:
host=??? port=???
... bench@postgres/pgbench ??? LOG: connection ready:
setup total=20033.681 ms, fork=0.540 ms, authentication=5.641 ms
When the lookup fails, PostgreSQL logs a WARNING and writes ??? in place of the
client's host and port. The IP address the server already had does not appear anywhere in
the log. It stays visible only in pg_stat_activity.client_addr, and only while
the session is open. The last line also shows where the time went: 20 seconds of setup, of
which authentication took 5.6 ms.
The cost does not depend on what you log. In one run we used the upstream default
log_line_prefix and turned connection logging off, so nothing in the log could
show a host name. Each connection still waited 20 seconds, and the only trace in the log was
one WARNING line per connection.
Connection pools hide the cost until they reconnect
The lookup happens once per connection, not once per query. In one 30-second run per setting with eight connections that stayed open, the server answered 19,291 queries per second with log_hostname off and 19,165 with it on. One run per setting cannot show whether a 0.7% difference is real, but the mechanism explains why it should be small: once a connection exists, it makes no more lookups.
This is why a connection pool can hide the setting for a long time. The cost comes back when the pool opens new connections - for example after a database restart or a failover, when every connection in the pool is opened again at about the same time, and each one makes its own lookup.
How to check your server
Connect to your database with psql or any other SQL client you normally use, and run:
SELECT name, setting, source
FROM pg_settings
WHERE name IN ('log_hostname', 'log_line_prefix', 'log_connections');
- log_hostname is off? There is no lookup and nothing to change. If you
want the client address in the log, add
%htolog_line_prefix, as shown below. - log_hostname is on? Check whether anything uses the names: the log
line prefix (
%hor%r), connection logging, or reports that readclient_hostnamefrompg_stat_activity. If nothing does, the setting costs a lookup per connection and shows nothing in return. If something does, weigh what the names give you against the waiting time described above.
To turn it off - in an SQL session, the same way as above, connected as a superuser:
ALTER SYSTEM SET log_hostname = off; SELECT pg_reload_conf();
The change needs a reload, not a restart, and applies to connections opened after the
reload. To get the client address on every log line, add %h to
log_line_prefix after %q. %q itself prints nothing:
it tells background processes, which have no client, to stop at that point of the prefix.
For example, in an SQL session as a superuser:
ALTER SYSTEM SET log_line_prefix = '%m [%p] %q%u@%d/%a %h '; SELECT pg_reload_conf();
This prefix only demonstrates where %h goes; it is not a recommendation for
your whole prefix. Keep the prefix you have and add %h to it, and update every
tool that reads your log - log analyzers, alert rules - so it knows the new format.
To confirm the result, open a new connection and check that its log lines show the IP
address in the prefix. On PostgreSQL 18 you can also measure connection setup on your own
server: when log_connections includes setup_durations, the server
logs "connection ready: setup total=..." for every new connection. That is one more log line
per connection, so use it for a measurement window, not forever, on servers that open many
connections.
On managed services, PostgreSQL settings live in the provider's console - a parameter group on Amazon RDS, server parameters on Azure Database for PostgreSQL, database flags on Google Cloud SQL - and the provider decides which of them you can change. Check what your server actually runs with the query above.
If you need host names
Sometimes a name is useful - for example, when client IP addresses change often and you keep logs for a long time. In that case, resolve the addresses when you read the log, not while clients wait to connect: an IP address in the log can be looked up later, during an investigation. Keep in mind that the answer then reflects DNS at the time you ask, not at the time of the connection.
Two more notes. Behind a connection pooler, %h shows the pooler's address,
whatever log_hostname says. And to see which application a log line belongs to, use
application_name: the client sets it, %a prints it, and it needs no
lookup.
How we tested
Two Azure virtual machines in the Poland Central region, in their own private network: the database server on Standard_D4s_v5 (Ubuntu 24.04, glibc 2.39, PostgreSQL 18.6 from the PostgreSQL project's packages) and the client on Standard_D2s_v5. PostgreSQL settings as installed by the packages, apart from the log line prefix, connection logging, log file handling and the listen address. Client-side numbers come from pgbench 18.6; server-side numbers from the PostgreSQL 18 log message "connection ready: setup total=..." of the benchmark's own connections. Slow DNS: 100 ms of delay added to every packet from the database server to Azure DNS (tc netem). DNS down: Azure DNS answers dropped at the database server's firewall (iptables), so the questions left and no answer came back. The healthy-DNS variants ran three times each, in alternating order; the slow, failing, eight-client and persistent-connection scenarios ran once each. The local DNS cache was emptied before every run.
In short
%hinlog_line_prefixlogs the client address. It needs no lookup.- log_hostname changes what
%hprints, from the IP address to a name from DNS. It costs one DNS lookup per new connection, also when nothing prints the name. - When DNS is slow, every new connection is slow. When DNS is down, every new connection
waits until the resolver gives up, and the log shows
???instead of the address. - Leaving log_hostname off, the default, avoids all of this. If you need names, resolve them when you read the log.