How PostgreSQL log_hostname Can Slow Down New Connections
November 2026
New TCP connections can take longer when PostgreSQL’s log_hostname setting is enabled. It adds a reverse DNS lookup before authentication, so the delay can range from milliseconds to seconds, depending on DNS. The %h escape in log_line_prefix can record the client’s IP address without a lookup; log_hostname changes it to a hostname obtained through DNS. Guardioz’s PostgreSQL 18 tests show how the settings work together and what that lookup cost when DNS was healthy, slow, or unavailable.
These three entries come from one connection in the original PostgreSQL 18.6 test. The prefix ends with %h. The address is the client’s IP, not the server’s, and PostgreSQL recorded it without a DNS lookup.
Two settings that work together
The server log can identify where a connection came from. Two settings control this information, but they have different roles.
🟣 %h is an escape in log_line_prefix, the text PostgreSQL places at the beginning of each log line. It represents the remote host name or IP address. On lines belonging to a client session, it shows where that client connected from.
🟣 log_hostname is a switch, disabled by default. It determines the value printed by %h: the client’s IP address when off, or a hostname obtained through DNS when on.
PostgreSQL 18’s documentation (PostgreSQL: Documentation: 18: 19.8. Error Reporting and Logging) warns that hostname resolution can impose a non-negligible performance cost, depending on the resolution setup.
Two practical details are important. log_hostname does not introduce its own field. Without %h, or %r to include the port, ordinary log lines do not identify the client, regardless of log_hostname. The upstream default prefix, ‘%m [%p] ‘, contains neither. The switch also changes host= in the “connection received” message when connection logging is enabled, and populates pg_stat_activity.client_hostname. In the reported tests, the hostname replaced the IP address in the log rather than appearing beside it.
What the lookup does, and when it runs
A reverse DNS lookup asks which name is associated with an IP address, using a PTR record. With log_hostname enabled, PostgreSQL performs it for every new TCP connection immediately after that connection’s server process starts, before reading the first client message and before authentication. The connection waits for DNS to respond or for the system resolver to stop trying. Local Unix-socket connections do not perform this lookup.
The lookup runs whenever the switch is on, even if no part of the configuration prints the name. Its duration is also outside authentication_timeout. A comment beside the lookup in the PostgreSQL 18 source (postgres/src/backend/tcop/backend_startup.c at REL_18_STABLE · postgres/postgres) explicitly excludes PreAuthDelay and the preceding DNS interactions from that time limit.
The cost with healthy DNS
The original measurements used two Microsoft Azure virtual machines in one private network: a PostgreSQL 18.6 server on Ubuntu 24.04, Standard_D4s_v5 with 4 vCPUs, and a separate Standard_D2s_v5 client. Connections used TLS 1.3 and SCRAM-SHA-256 password authentication, as many production environments do. The client ran pgbench, PostgreSQL’s bundled benchmark tool, with a custom script containing SELECT 1 and the -C option. This opens a new connection for each transaction, making connection setup most of the work. That is the purpose of this experiment, not the throughput of a real application workload.
Azure DNS returned vm-logtalk-hostname-cli.internal.cloudapp.net for the client’s address. Ubuntu uses systemd-resolved as a local DNS cache by default. Without a local cache, each lookup reaches the DNS server, so the database host was tested both with and without caching.
The table reports two values from pgbench. Connection time is the average duration of opening a connection. Connections per second counts how often the client connected, executed SELECT 1, and disconnected. pgbench calls this transactions per second; with -C, each transaction corresponds to one connection. With a single client, the next connection begins only after the preceding one ends. Longer connections therefore mean fewer connections per second. This is the rate of one client repeatedly reconnecting, not the maximum rate the server can accept.
Without a local cache, each connection took about 6 ms longer: 43% more time per connection and 29% fewer connections per second in this test. Server-side timing supported the same result. PostgreSQL 18 can report setup duration through log_connections with setup_durations. Average setup time increased from 10.4 ms to 16.3 ms, while authentication remained at 6.0 ms in all three variants. The additional time occurred before authentication, where the DNS lookup runs.
With the local cache, the difference was 0.3 ms. Azure DNS assigned the answer a 10-second TTL, so most lookups were served from the cache. The roughly 6 ms represented a round trip to Azure DNS from that server. Another network will have a different figure: the time its DNS server takes to reply.
The cost with slow or failing DNS
Healthy DNS is the best case. The original experiment next delayed DNS and then made it unreachable, affecting only DNS traffic at the database server, before repeating the same one-client test.
With log_hostname off, DNS problems did not affect this test because no lookup was needed. With it on, every new connection waited. Delaying DNS by 100 ms added about 109 ms per connection. Around 106 ms was the lookup: the added 100 ms plus the approximately 6 ms observed with healthy DNS. The remaining 2–3 ms were distributed across other setup work; a single run cannot explain that difference. When no DNS response arrived, a connection waited until the resolver stopped trying. Direct DNS requests from glibc 2.39 on Ubuntu 24.04 took 20 seconds, with four requests sent five seconds apart. Through systemd-resolved, the wait was 10 seconds. Resolver options can change those durations, but the dependency remains: connection setup waits for DNS to answer or fail. With eight clients connecting concurrently and DNS down, the server completed 0.2 new connections per second. With log_hostname off and healthy DNS, those same eight clients completed 160 per second. These results describe that reported test, not a universal capacity limit.
What Happens When Reverse DNS Fails?
The following entries are from one of the reported 20-second connections, with %h in the prefix. Timestamps and process IDs are replaced by …; long entries are wrapped for readability.
In that test, a failed lookup produced a WARNING and ??? in place of the client’s host and port. The IP address already known to the server was absent from the log. It remained available in pg_stat_activity.client_addr only for as long as the session stayed open. The final entry also identifies the delay: around 20 seconds of setup, including only 5.6 ms of authentication.
The cost did not depend on what the log displayed. Another run used the upstream default log_line_prefix with connection logging disabled, leaving no way for the log to print a hostname. Connections still waited 20 seconds. The only log evidence was one WARNING per connection.
Why a Logged Hostname Is Not Proof of Identity
When log_hostname is on, PostgreSQL records the PTR name it receives without checking that the name resolves back to the same IP. A PTR record belongs to the IP address rather than the domain named in it. Whoever manages reverse DNS for that address, usually the network owner or provider, sets it; the named domain’s owner does not control it. The party controlling the client’s reverse DNS can therefore choose the name shown in the log, including a name that resembles another IP address.
The source article checked this separately on PostgreSQL 18.6 using a controlled DNS server. It assigned the PTR value 10.0.0.5 to client address 127.0.0.5, and db-backup-01.corp.example, a name without an address, to 127.0.0.6. These five entries came from three connections, with %h at the end of the prefix and timestamps and process IDs replaced by … .
Every prefix and the connection-received message used the PTR name. In this test, the real address appeared only inside a FATAL refusal message and in pg_stat_activity.client_addr while a session was open.
Get Guardioz’s stories in your inbox
Join Medium for free to get updates from this writer.
For access control, PostgreSQL does not accept that name without verification. As the PostgreSQL 18 documentation (PostgreSQL: Documentation: 18: 20.1. The pg_hba.conf File) explains, a hostname rule in pg_hba.conf matches only if forward resolution also leads back to the client’s address. The final connection was rejected because the pg_hba.conf rule for db-backup-01.corp.example could not match a name with no address. The prefix displayed the name nonetheless.
During an investigation, a logged hostname is therefore a reverse-DNS answer, not proof of the client’s identity. With log_hostname off, %h records the connecting IP. If a name is needed later, resolve the address and check that the name resolves back to the same IP, as pg_hba.conf does and logging does not.
Connection pools conceal the cost until reconnection
Resolution happens once per connection, not per query. In one 30-second run per setting with eight persistent connections, the reported server handled 19,291 queries per second with log_hostname off and 19,165 with it on. One run per setting cannot establish whether the 0.7% difference is real. The mechanism explains why the effect should be small: an established connection performs no additional lookups.
A connection pool can consequently keep the cost out of view for a long time. It returns when the pool creates database connections, for example after a restart or failover. At those times, many pool connections may reopen together, and each performs its own lookup.
How to check your server
Connect using psql or your usual SQL client and run:
🟣 If log_hostname is off, this setting performs no lookup and needs no change. To include the client address in the log, add %h to log_line_prefix as described below.
🟣 If it is on, check whether the names are used by %h or %r in the prefix, by connection logging, or by reports reading pg_stat_activity.client_hostname. If none uses them, each connection pays for a lookup without displaying its result. If they are used, compare the value of the names with the waiting time described above.
To disable it, run these commands in a superuser SQL session:
This needs a configuration reload, not a restart, and affects connections opened afterward. To include the client address on each log line, add %h after %q in log_line_prefix. %q prints nothing; it tells background processes, which have no client, to stop reading the prefix at that point. The following superuser-session example shows the placement:
This is only an example of where to put %h, not a recommendation to replace the entire prefix. Retain the existing prefix, extend it with %h, and update the tools that consume your logs, including log analyzers and alert rules, for the changed format.
Open a new connection to confirm that its log entries show the IP address in the prefix. PostgreSQL 18 can also measure setup time on your server: adding setup_durations to log_connections produces a connection-ready entry with setup total for each new connection. That adds one line per connection, so on servers with frequent connections use it during a measurement window rather than permanently.
Managed services expose settings through their provider’s configuration system: parameter groups on Amazon RDS, server parameters on Azure Database for PostgreSQL, and database flags on Google Cloud SQL. The provider determines which settings can be changed. Use the query above to inspect the values actually running on the server.
If you need host names
Names can be useful, for example when client IPs change often and logs are retained for a long time. In that case, resolve addresses while reading the log instead of making clients wait during connection setup. A stored IP can be looked up during an investigation, but the answer describes DNS when the lookup is made, not necessarily when the connection occurred.
Two further points: behind a connection pooler, %h identifies the pooler’s address regardless of log_hostname. To identify the application associated with a log line, use application_name. The client supplies it, %a prints it, and no lookup is required.
How the tests were performed
The original experiment used two Azure VMs in Poland Central in a dedicated private network. The database host was Standard_D4s_v5 with Ubuntu 24.04, glibc 2.39, and PostgreSQL 18.6 from the PostgreSQL project’s packages; the client was Standard_D2s_v5. Settings remained as installed by the packages except for the prefix, connection logging, log-file handling, and listen address. Client figures came from pgbench 18.6. Server figures came from PostgreSQL 18 connection-ready setup-total messages for the benchmark’s own connections. For slow DNS, tc netem added 100 ms to every packet from the database host to Azure DNS. For DNS failure, iptables on the database host dropped Azure DNS answers: requests went out, but no answer returned. Each healthy-DNS variant ran three times in alternating order. The slow-DNS, failing-DNS, eight-client, and persistent-connection scenarios each ran once. The local DNS cache was cleared before every run. The unchecked-name experiment was separate: PostgreSQL 18.6 and dnsmasq ran on one Linux machine in an isolated network namespace, with the system resolver using DNS only and clients connecting from 127.0.0.5 and 127.0.0.6.
The cases from this article in one picture. Times are from our test: pgbench -C, no local DNS cache.
In short
🟣 %h in log_line_prefix records the client address without requiring a lookup.
🟣log_hostname changes the value from an IP address to a DNS name. It adds one lookup per new TCP connection, even when the name is never printed.
🟣In the reported tests, slow DNS delayed every new connection. When DNS did not answer, connections waited for the resolver to give up, and the log showed ??? instead of the address.
🟣 The logged name is not verified. Whoever controls reverse DNS for the client’s address chooses the PTR name.
🟣Keeping log_hostname off, its default, avoids this logging-related lookup. Resolve names while reading the logs if they are needed.
Want a real say in what we build?
Guardioz is in beta. You can sign up, and the product is early — we say so up front. Always free for one user. No credit card required. Every customer can influence what we build next: which problems matter most, which recommendations earn trust, and how the product develops.
➡️Start free: https://app.guardioz.com/register
➡️See pricing: https://guardioz.com/pricing
➡️ Questions first? Write to contact@guardioz.com.







