CentOS 8's systemd-resolved resolv.conf symlink: mixed resolver behavior and TLS trust mismatches
0 reputation · 21 Jul 2024, 09:02 UTC
0 reputation · 21 Jul 2024, 09:02 UTC
On CentOS 8, systemd-resolved is active by default and manages /etc/resolv.conf as a symlink to /run/systemd/resolve/stub-resolv.conf, with the stub listener on 127.0.0.53. I need a reliable way to troubleshoot name resolution and TLS certificate validation across tools that may not all consult the same resolver path.
Some utilities read /etc/resolv.conf directly and may only see updates after the symlink target is refreshed, while glibc-based applications go through systemd-resolved immediately. NetworkManager restarts of systemd-resolved may also leave stale cache entries in place until expiry or manual clearing. Separately, certificate validation uses the system trust store maintained by update-ca-trust and p11-kit, but Java applications may still default to their own legacy cacerts file, so a hostname that resolves correctly can still fail TLS verification against a different trust anchor set.
update-ca-trust extract?31775 reputation · 21 Jul 2024, 14:04 UTC
Short answer: on CentOS 8 there is no single "resolver" — there are at least three paths (glibc NSS, direct /etc/resolv.conf readers, and non-glibc resolvers), plus a separate trust-store split between the p11-kit system bundle and the JVM's own cacerts. You diagnose contradictions by testing each path independently, not by assuming the symlink unifies behavior.
Note on scope: this describes final CentOS 8 (now EOL) behavior with its shipped systemd/NetworkManager versions. CentOS Stream and RHEL rebuilds may differ; verify against your installed packages.
Confirmed facts first: /etc/resolv.conf is a symlink to /run/systemd/resolve/stub-resolv.conf, pointing at the 127.0.0.53 stub listener. But reading that file and using systemd-resolved are different things:
hosts: line in /etc/nsswitch.conf. With resolve listed, they talk to systemd-resolved over its varlink/D-Bus interface directly; with dns, they read resolv.conf and hit the stub.The practical test is a three-way comparison for the problem hostname:
getent hosts app.example.com # NSS path (what most glibc apps see)
dig @127.0.0.53 app.example.com # stub listener path
dig @192.0.2.10 app.example.com # your actual upstream DNSIf getent and the stub disagree, NSS is configured differently than you think — check nsswitch.conf. If the stub and upstream disagree, systemd-resolved is using a different link's DNS server than expected; run resolvectl status to see per-link DNS servers. If all three agree but one specific binary still fails, that binary is bypassing NSS (common with Go's pure resolver) — check its docs or force the cgo resolver.
Normally a full restart of systemd-resolved drops its cache, so persistent stale answers after a restart are more likely a per-link server change than cache. Before blaming upstream DNS, confirm with counters rather than guesswork:
resolvectl statistics # note cache hit/miss counts
resolvectl flush-caches
getent hosts app.example.com # re-test
resolvectl statistics # misses should have incrementedIf flushing changes the answer, the cache was the culprit. If it doesn't, compare the stub query against the upstream query from step 1 — that isolates whether systemd-resolved is forwarding to the wrong server (a NetworkManager/link configuration issue) rather than serving stale data. Do not edit /etc/resolv.conf directly; as a managed symlink, changes are overwritten and can break the stub setup.
A hostname that resolves fine but fails TLS verification only in Java almost always means the JVM is validating against a different anchor set than the system. CentOS 8's system trust is maintained by update-ca-trust/p11-kit, with extracted bundles under /etc/pki/ca-trust/extracted/ — including a Java keystore at extracted/java/cacerts. Whether your JVM actually uses it depends on vendor packaging: some distro OpenJDK builds symlink the JVM cacerts to the p11-kit bundle; others ship their own file at $JAVA_HOME/lib/security/cacerts (or jssecacerts). Don't assume — check:
ls -l $JAVA_HOME/lib/security/cacerts # symlink to /etc/pki/... or a real file?
trust list | grep -i "YourCA" # is the CA in the system store?
keytool -list -cacerts -storepass changeit | grep -i "YourCA"Cross-check behavior: if curl -v https://app.example.com succeeds (system store) while the Java app fails, the trust stores diverge. Then either add the CA via update-ca-trust (place the PEM in /etc/pki/ca-trust/source/anchors/ and run update-ca-trust extract) if the JVM is linked to the system bundle, or import it into the JVM keystore — or point the app at the extracted bundle with -Djavax.net.ssl.trustStore=/etc/pki/ca-trust/extracted/java/cacerts.
If the failing Java process runs in a container, none of the host-level checks apply — the container image carries its own resolv.conf, nsswitch.conf, and cacerts. Confirm whether the app is containerized before modifying host trust.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.