Two name problems appear on almost every small OpenVPN gateway. You cannot reach a connected laptop by the name on its certificate, and the services behind the tunnel have no names at all. They look like one problem and they need opposite treatments, because one address moves with every session and the other does not. Decide which kind of name you are adding before you edit anything.

Decide per name, not per gateway

The test is simple: does this address change when a client reconnects? If yes, the name has to be generated from the VPN session. If no, it belongs in a static entry you can read and review.

Name kindSource of truthWhere it lives
Connected clientThe address OpenVPN just assignedGenerated hosts file, rewritten per session
Service behind the tunnelA fixed address you choseStatic address= drop-in
Anything with a certificate nameThe certificate, not the poolEither, but the name must match the certificate

Mixing them is the usual failure. A service pinned into the generated file disappears the first time the script rewrites it, and a client written into a static entry points at whoever held that address last. Both mistakes stay invisible until a name resolves to the wrong machine. By then the person debugging it assumes DNS is broken rather than the file.

The dynamic path: learn-address and the pool

OpenVPN knows the assignment, so let it tell you. These server options make the assignment durable and scriptable:

client-config-dir /etc/openvpn/ccd
ifconfig-pool-persist /etc/openvpn/ip-pool 60
script-security 2
learn-address /etc/openvpn/learn-address.sh

learn-address runs your script whenever an address is added, updated or deleted, and passes the operation, the address and the common name. A minimal script appends one line per client and removes it on delete:

#!/bin/sh
HOSTS=/etc/openvpn/hosts.lan
case "$1" in
  add|update) echo "$2 $3" >> "$HOSTS" ;;
  delete)     sed -i "/^$2 /d" "$HOSTS" ;;
esac
kill -HUP $(cat /var/run/dnsmasq.pid)

On a tun device the second argument is the client's tunnel address, so the line is directly usable. On a tap device OpenVPN passes the client MAC address instead, because a tap segment can carry protocols other than IPv4, and then you have to look the address up in the pool file by common name before writing anything.

Be clear about what the generated file is: a snapshot of who is connected right now. Names vanish on disconnect, which is exactly what you want for laptops that move between networks, and exactly what you do not want for a server that is up all night.

The static path: address= and domain expansion

Point dnsmasq at the generated file instead of the system hosts file, and give it a domain so short names expand into fully qualified ones:

domain=corp.example
no-hosts
addn-hosts=/etc/openvpn/hosts.lan
listen-address=127.0.0.1,10.8.0.1
bind-interfaces

With domain set, a line reading 10.8.0.6 alice answers for both alice and alice.corp.example. Keep listen-address to the tunnel gateway and loopback, because a resolver published on the LAN interface by accident is a service you did not mean to offer.

Services that never come from the pool get their own entries. Put them in a separate drop-in file under the distribution's configuration directory so a package upgrade cannot overwrite them:

address=/files.corp.example/10.8.0.20
address=/db.corp.example/10.9.0.5

Hand the resolver over or nothing happens

A resolver nobody queries changes nothing. The server has to advertise it:

push "dhcp-option DNS 10.8.0.1"
push "dhcp-option DOMAIN corp.example"

Windows clients apply both options themselves. Most Linux clients do not: they need a supported resolver integration such as the distribution's update-resolv-conf hook or NetworkManager's DNS handling, or a manual search domain and nameserver. Check the client's actual resolver instead of assuming the push was honoured, because a push that is ignored looks exactly like a server that is not answering.

Verify at three layers

Ask the server directly first, then a client, then the application:

dig +short @10.8.0.1 alice.corp.example
getent hosts files.corp.example

If direct queries answer and the application does not, the fault is the client handoff rather than the zone. If the name resolves and the connection still fails, the name is fine and the route or firewall is the next thing to look at.

Write the two categories down next to the configuration: which names are generated, which are pinned, and which certificate each one has to match. The next person to add a server will copy whatever pattern they find first.

Artihkkalat mat laktásit