Ubuntu 서버에서 DNS 설정하기

Ubuntu 서버에서 DNS 서버를 바꾸려고 /etc/resolv.conf를 열면 nameserver 127.0.0.53 한 줄만 보이고, 직접 고쳐도 재부팅하면 원래대로 돌아간다. 18.04부터 이름 해석을 systemd-resolved가 맡고 있기 때문이다. 실제 업스트림 DNS는 Netplan(DHCP 포함)이나 resolved.conf에서 정한다. 이 글에서는 Ubuntu 24.04 서버에서 현재 DNS 확인, 임시 변경, Netplan으로 영구 설정, 전역 DNS와 DNS over TLS, /etc/hosts와 캐시 동작을 실제 실행 결과로 정리한다.

현재 DNS 구성 확인

/etc/resolv.conf는 resolved가 만든 stub 파일을 가리키는 심볼릭 링크이고, 애플리케이션은 로컬 stub 리졸버 127.0.0.53에만 질의한다.

$ ls -l /etc/resolv.conf
lrwxrwxrwx 1 root root 39 Apr 23  2024 /etc/resolv.conf -> ../run/systemd/resolve/stub-resolv.conf

$ grep -v '^#' /etc/resolv.conf | grep -v '^$'
nameserver 127.0.0.53
options edns0 trust-ad
search .

실제 업스트림 서버는 resolvectl로 본다. 이 호스트는 DHCP로 받은 KT DNS를 쓰고 있다.

$ resolvectl status enp0s3
Link 2 (enp0s3)
    Current Scopes: DNS
         Protocols: +DefaultRoute -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
Current DNS Server: 168.126.63.1
       DNS Servers: 168.126.63.1 168.126.63.2

$ cat /run/systemd/resolve/resolv.conf | grep -v '^#' | grep -v '^$'
nameserver 168.126.63.1
nameserver 168.126.63.2
search .

/run/systemd/resolve/resolv.conf는 stub을 거치지 않고 업스트림을 직접 적은 파일이다. 링크 대상에 따라 동작 모드가 달라진다.

/etc/resolv.conf 링크 대상resolv.conf mode동작
../run/systemd/resolve/stub-resolv.confstub (기본값)127.0.0.53으로 질의, resolved 캐시·라우팅 사용
../run/systemd/resolve/resolv.confuplink업스트림 서버로 직접 질의, resolved 캐시 우회
일반 파일foreignresolved가 관리하지 않음. 직접 작성한 내용 그대로 사용

조회는 resolvectl query로 하면 어느 링크와 프로토콜로 답을 받았는지까지 나온다. dig는 기본적으로 stub(127.0.0.53)에 질의한다.

$ resolvectl query ubuntu.com
ubuntu.com: 2001:67c:1562::1f                               -- link: enp0s3
            2620:2d:4000:1::27                              -- link: enp0s3
            ...
            185.125.190.20                                  -- link: enp0s3
            91.189.91.44                                    -- link: enp0s3

-- Information acquired via protocol DNS in 319.6ms.
-- Data is authenticated: no; Data was acquired via local or encrypted transport: no
-- Data from: network

$ dig ubuntu.com | grep -E 'SERVER|Query time'
;; Query time: 0 msec
;; SERVER: 127.0.0.53#53(127.0.0.53) (UDP)

resolvectl로 임시 변경

링크별 DNS 서버와 검색 도메인을 런타임에만 바꾼다. /etc/resolv.conf의 search도 함께 바뀐다.

$ sudo resolvectl dns enp0s3 1.1.1.1 8.8.8.8
$ sudo resolvectl domain enp0s3 lab.example

$ resolvectl status enp0s3
Link 2 (enp0s3)
    Current Scopes: DNS
         Protocols: +DefaultRoute -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
       DNS Servers: 1.1.1.1 8.8.8.8
        DNS Domain: lab.example

$ grep -v '^#' /etc/resolv.conf | grep -v '^$'
nameserver 127.0.0.53
options edns0 trust-ad
search lab.example

revert로 되돌리면 DHCP 값으로 복귀한다. 재부팅이나 netplan apply 때도 사라지므로 장애 대응용으로만 쓴다.

$ sudo resolvectl revert enp0s3
$ resolvectl status enp0s3 | grep -E 'DNS Servers|DNS Domain'
       DNS Servers: 168.126.63.1 168.126.63.2

Netplan으로 영구 설정

cloud-init이 만든 50-cloud-init.yaml은 건드리지 않고 뒤 번호 파일을 추가한다. Netplan은 파일을 이름순으로 병합한다.

network:
  version: 2
  ethernets:
    enp0s3:
      dhcp4-overrides:
        use-dns: false
        use-domains: false
      nameservers:
        addresses: [1.1.1.1, 9.9.9.9]
        search: [lab.example]
$ sudo chmod 600 /etc/netplan/60-dns.yaml
$ sudo netplan get ethernets.enp0s3
nameservers:
  addresses:
  - 1.1.1.1
  - 9.9.9.9
  search:
  - lab.example
dhcp4: true
dhcp4-overrides:
  use-dns: false
  use-domains: false

$ sudo netplan apply
$ resolvectl status enp0s3
Link 2 (enp0s3)
    Current Scopes: DNS
         Protocols: +DefaultRoute -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
Current DNS Server: 9.9.9.9
       DNS Servers: 1.1.1.1 9.9.9.9
        DNS Domain: lab.example

netplan get은 두 파일이 병합된 결과다. 실제로 networkd에 넘어간 설정은 /run/systemd/network/에서 확인한다.

$ sudo cat /run/systemd/network/10-netplan-enp0s3.network
[Match]
Name=enp0s3

[Network]
DHCP=ipv4
LinkLocalAddressing=ipv6
DNS=1.1.1.1
DNS=9.9.9.9
Domains=lab.example

[DHCP]
RouteMetric=100
UseMTU=true
UseDNS=false
UseDomains=false

dhcp4-overrides를 빼면 DHCP 서버가 사라지지 않고 뒤에 합쳐진다. DHCP DNS를 완전히 대체하려면 use-dns: false가 필요하다.

network:
  version: 2
  ethernets:
    enp0s3:
      nameservers:
        addresses: [1.1.1.1, 9.9.9.9]
$ sudo netplan apply
$ resolvectl status enp0s3 | grep 'DNS Servers'
       DNS Servers: 1.1.1.1 9.9.9.9 168.126.63.1 168.126.63.2

고정 IP 서버라면 dhcp4: false와 addresses/routes 옆에 같은 nameservers 블록을 둔다. Netplan 전반은 Ubuntu Netplan으로 네트워크 설정 관리하기에 정리했다.

전역 DNS와 DNS over TLS

인터페이스와 무관한 전역 서버는 resolved.conf drop-in으로 지정한다. # 뒤 이름은 TLS 인증서 검증용 서버 이름이다.

[Resolve]
DNS=1.1.1.1#cloudflare-dns.com 1.0.0.1#cloudflare-dns.com
DNSOverTLS=yes
Domains=~.
$ sudo systemctl restart systemd-resolved
$ resolvectl status
Global
         Protocols: -LLMNR -mDNS +DNSOverTLS DNSSEC=no/unsupported
  resolv.conf mode: stub
       DNS Servers: 1.1.1.1#cloudflare-dns.com 1.0.0.1#cloudflare-dns.com
        DNS Domain: ~.

Link 2 (enp0s3)
    Current Scopes: DNS
         Protocols: +DefaultRoute -LLMNR -mDNS +DNSOverTLS DNSSEC=no/unsupported
       DNS Servers: 168.126.63.1 168.126.63.2

$ resolvectl query ubuntu.com | tail -3
-- Information acquired via protocol DNS in 269.8ms.
-- Data is authenticated: no; Data was acquired via local or encrypted transport: yes
-- Data from: network

$ sudo ss -tnp state established '( dport = :853 )'
Recv-Q Send-Q Local Address:Port  Peer Address:PortProcess
0      0       192.168.0.46:60002      1.1.1.1:853  users:(("systemd-resolve",pid=26306,fd=21))

“encrypted transport: yes”와 853 포트 연결로 DoT가 확인된다. 실제로 어느 서버에 질의했는지는 디버그 로그로 본다.

$ sudo resolvectl log-level debug
$ resolvectl query example.org | tail -2
-- Data is authenticated: no; Data was acquired via local or encrypted transport: yes
-- Data from: network
$ sudo journalctl -u systemd-resolved --since -20s | grep 'Using DNS server' | sort -u
... systemd-resolved[26306]: Using DNS server 1.1.1.1#cloudflare-dns.com for transaction 32025.
... systemd-resolved[26306]: Using DNS server 1.1.1.1#cloudflare-dns.com for transaction 31591.
$ sudo resolvectl log-level info

같은 설정에서 Domains=~.만 지우면 링크의 DHCP 서버에도 질의가 나간다. 링크도 +DefaultRoute라 둘이 동등한 후보가 되기 때문이다.

$ sudo journalctl -u systemd-resolved --since -10s | grep 'Using DNS server' | sort -u
... systemd-resolved[26586]: Using DNS server 1.1.1.1#cloudflare-dns.com for transaction 28129.
... systemd-resolved[26586]: Using DNS server 1.1.1.1#cloudflare-dns.com for transaction 60138.
... systemd-resolved[26586]: Using DNS server 168.126.63.1 for transaction 10283.
... systemd-resolved[26586]: Using DNS server 168.126.63.1 for transaction 6472.

자주 쓰는 [Resolve] 옵션은 다음과 같다.

옵션예시의미
DNS=1.1.1.1#cloudflare-dns.com전역 DNS 서버(#뒤는 TLS 서버 이름)
FallbackDNS=9.9.9.9어떤 링크에도 DNS가 없을 때만 사용
Domains=~. / corp.example / ~corp.example~.는 모든 질의를 전역 서버로, 일반 값은 검색 도메인, ~값은 해당 도메인만 라우팅(split DNS)
DNSOverTLS=yes / opportunistic / noyes는 TLS 실패 시 질의 실패, opportunistic은 평문으로 후퇴
DNSSEC=allow-downgrade / yes / no서명 검증. 지원 안 하는 업스트림에서 yes는 해석 실패를 부른다
Cache=yes / no-negative / no응답 캐시
ReadEtcHosts=yes/etc/hosts 항목을 stub 응답에 포함
DNSStubListener=yes / no127.0.0.53:53 리스너. 다른 DNS 서버를 53번에 띄우려면 no

/etc/hosts와 stub의 관계

nsswitch.conf 순서대로 files(/etc/hosts)가 DNS보다 먼저다. resolved stub도 /etc/hosts를 읽기 때문에 dig 결과에도 나온다.

$ grep ^hosts /etc/nsswitch.conf
hosts:          files mdns4_minimal [NOTFOUND=return] dns

$ echo '10.10.0.5 db.lab.example db' | sudo tee -a /etc/hosts
10.10.0.5 db.lab.example db

$ getent hosts db.lab.example
10.10.0.5       db.lab.example db

$ resolvectl query db.lab.example
db.lab.example: 10.10.0.5

-- Information acquired via protocol DNS in 1.7ms.
-- Data is authenticated: yes; Data was acquired via local or encrypted transport: yes
-- Data from: synthetic

$ dig +short db.lab.example
10.10.0.5

$ dig +short @1.1.1.1 db.lab.example

$ dig db.lab.example @1.1.1.1 | grep status
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 32367

“Data from: synthetic”은 네트워크가 아니라 hosts에서 만든 답이라는 뜻이다. 업스트림 서버 자체를 시험하려면 dig @서버로 stub을 우회한다.

캐시 확인과 비우기

DNS 레코드를 바꿨는데 예전 IP가 나오면 캐시부터 비운다. statistics는 root 권한이 필요하다.

$ sudo resolvectl statistics | sed -n '/^Cache/,/Misses/p'
Cache
                         Current Cache Size:  4
                                 Cache Hits:  6
                               Cache Misses: 10

$ sudo resolvectl flush-caches
$ sudo resolvectl statistics | sed -n '/^Cache/,/Misses/p'
Cache
                         Current Cache Size:  0
                                 Cache Hits:  6
                               Cache Misses: 10

주의사항

증상원인조치
/etc/resolv.conf 수정이 사라짐stub 파일로의 심볼릭 링크라 resolved가 다시 씀Netplan nameservers 또는 resolved.conf.d drop-in으로 설정
설정한 DNS 뒤에 DHCP DNS가 붙음DHCP 서버가 병합됨Netplan dhcp4-overrides에 use-dns: false
전역 DNS를 넣었는데 DHCP DNS로도 질의전역과 링크가 모두 기본 라우트resolved.conf에 Domains=~.
DoT 설정 후 해석이 전부 실패업스트림이 853을 지원하지 않거나 #서버이름 불일치지원 서버 사용, 임시로 DNSOverTLS=opportunistic
53번 포트를 쓰는 DNS 서버(dnsmasq, bind)가 안 뜸127.0.0.53:53을 stub 리스너가 점유DNSStubListener=no 후 resolv.conf 링크를 uplink 파일로 교체
netplan apply 후 경고yaml 파일 권한이 600이 아님sudo chmod 600 /etc/netplan/*.yaml
resolvectl statistics: Permission denied모니터링 소켓은 root 전용sudo로 실행

원격 서버에서 netplan apply는 링크를 다시 구성하므로 콘솔 접근이 가능한 상태에서 시도한다.

마무리

Ubuntu 서버의 DNS는 “/etc/resolv.conf는 stub, 실제 서버는 Netplan 또는 resolved.conf“로 기억하면 된다. 링크별 서버는 Netplan, 전역 서버나 DoT는 drop-in으로 두고, 결과는 항상 resolvectl status와 resolvectl query로 확인한다.

참고

답글 남기기