<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Pro Gineous</title>
    <description>The latest articles on DEV Community by Pro Gineous (@progineous).</description>
    <link>https://dev.to/progineous</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4185815%2Fbf56a66c-225e-4472-b46d-ce669c2bad76.png</url>
      <title>DEV Community: Pro Gineous</title>
      <link>https://dev.to/progineous</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/progineous"/>
    <language>en</language>
    <item>
      <title>How to Read dig Output When DNS Looks Wrong</title>
      <dc:creator>Pro Gineous</dc:creator>
      <pubDate>Sun, 18 Oct 2026 08:41:30 +0000</pubDate>
      <link>https://dev.to/progineous/how-to-read-dig-output-when-dns-looks-wrong-1115</link>
      <guid>https://dev.to/progineous/how-to-read-dig-output-when-dns-looks-wrong-1115</guid>
      <description>&lt;p&gt;You moved a site to a new server an hour ago and updated the A record. Your phone loads the new site, your laptop still shows the old one, and a teammate gets "server not found." Three devices, three different answers, and the DNS panel insists everything is correct.&lt;/p&gt;

&lt;p&gt;This is where the &lt;code&gt;dig&lt;/code&gt; command earns its place. A browser hides DNS behind layers of caching; &lt;code&gt;dig&lt;/code&gt; asks one specific server one specific question and prints exactly what came back. Once you can read that output, "DNS is broken" turns into a short list of facts: who answered, what they said, and how long they will keep saying it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;dig&lt;/code&gt; ships with macOS and most Linux distributions (on Debian and Ubuntu, install &lt;code&gt;dnsutils&lt;/code&gt;). The examples use &lt;code&gt;example.com&lt;/code&gt;; in our scenario the old server is &lt;code&gt;203.0.113.10&lt;/code&gt; and the new one is &lt;code&gt;203.0.113.20&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with one plain query
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dig example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;; &amp;lt;&amp;lt;&amp;gt;&amp;gt; DiG 9.18.30-0ubuntu0.24.04.2-Ubuntu &amp;lt;&amp;lt;&amp;gt;&amp;gt; example.com
;; global options: +cmd
;; Got answer:
;; -&amp;gt;&amp;gt;HEADER&amp;lt;&amp;lt;- opcode: QUERY, status: NOERROR, id: 48211
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 65494
;; QUESTION SECTION:
;example.com.                   IN      A

;; ANSWER SECTION:
example.com.            2917    IN      A       203.0.113.10

;; Query time: 3 msec
;; SERVER: 127.0.0.53#53(127.0.0.53) (UDP)
;; WHEN: Sat Oct 17 10:42:18 EEST 2026
;; MSG SIZE  rcvd: 56
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The screenshot adds a second query for a name that doesn't exist, so you can see &lt;code&gt;NXDOMAIN&lt;/code&gt; next to a normal answer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkygaqmfimye56as71rw7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkygaqmfimye56as71rw7.png" alt="Terminal screenshot of the dig command: dig example.com returning status NOERROR with an A record of 203.0.113.10 and 2917 seconds of TTL left, followed by dig shop.example.com returning NXDOMAIN with the SOA record in the AUTHORITY section" width="800" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It looks noisy, but it's only a few blocks.&lt;/p&gt;

&lt;h3&gt;
  
  
  The header: status and flags
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;-&amp;gt;&amp;gt;HEADER&amp;lt;&amp;lt;-&lt;/code&gt; line holds the most important word in the output: &lt;code&gt;status&lt;/code&gt;. Check it before looking at any IP. &lt;code&gt;NOERROR&lt;/code&gt; means the server answered without a problem, not that the answer is the one you wanted.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;flags&lt;/code&gt; line tells you what kind of server you were talking to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;qr&lt;/code&gt;: this is a response. Always there.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rd&lt;/code&gt;: you asked for recursion. dig does this by default.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ra&lt;/code&gt;: the server offers recursion, so you're talking to a resolver, not the source of the record.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aa&lt;/code&gt;: authoritative answer. The server holds the zone itself. You only see this when you query the domain's own nameservers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  QUESTION and ANSWER
&lt;/h3&gt;

&lt;p&gt;QUESTION echoes what you asked: name, class &lt;code&gt;IN&lt;/code&gt;, type &lt;code&gt;A&lt;/code&gt;. A typo or a forgotten &lt;code&gt;MX&lt;/code&gt; shows up here.&lt;/p&gt;

&lt;p&gt;ANSWER holds the records in five columns: name, TTL, class, type, value. Here the resolver says &lt;code&gt;example.com&lt;/code&gt; points to &lt;code&gt;203.0.113.10&lt;/code&gt;, the old server.&lt;/p&gt;

&lt;h3&gt;
  
  
  TTL is a countdown
&lt;/h3&gt;

&lt;p&gt;The second column is the TTL in seconds. On a resolver it's the time left before that cached copy expires, not the number you typed into your DNS panel. Run the query twice and you'll watch it drop. Above, 2917 seconds remain, so this resolver will hand out the old IP for roughly another 48 minutes, whatever you change in the meantime. On the authoritative server, the TTL is always the full configured value.&lt;/p&gt;

&lt;h3&gt;
  
  
  AUTHORITY, ADDITIONAL and the footer
&lt;/h3&gt;

&lt;p&gt;AUTHORITY mostly shows up when there's no answer. On an &lt;code&gt;NXDOMAIN&lt;/code&gt; or an empty response you'll find the zone's SOA record there, and its last number caps how long the "doesn't exist" result gets cached. When you query a TLD server, AUTHORITY is where the delegation (the NS records) lives. ADDITIONAL carries helpers such as nameserver IPs.&lt;/p&gt;

&lt;p&gt;In the footer, the &lt;code&gt;SERVER&lt;/code&gt; line matters. &lt;code&gt;127.0.0.53&lt;/code&gt; is systemd-resolved on your own machine, which means you're looking at your local cache, not at the internet.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the status codes are telling you
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;What it means&lt;/th&gt;
&lt;th&gt;Where to look next&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;NOERROR&lt;/code&gt; with answers&lt;/td&gt;
&lt;td&gt;The name exists and has records of that type&lt;/td&gt;
&lt;td&gt;Check the value and the TTL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;NOERROR&lt;/code&gt;, &lt;code&gt;ANSWER: 0&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;The name exists, but not with that record type (often called NODATA)&lt;/td&gt;
&lt;td&gt;Wrong type, or the record was never created&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NXDOMAIN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The name doesn't exist in the zone that answered&lt;/td&gt;
&lt;td&gt;A typo, a missing record, or a resolver still reading the old zone&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SERVFAIL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The resolver couldn't get a usable answer&lt;/td&gt;
&lt;td&gt;Broken delegation, unreachable nameservers, or a DNSSEC failure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;REFUSED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The server won't answer this query&lt;/td&gt;
&lt;td&gt;You asked a server that isn't authoritative for the zone and won't recurse for you&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On &lt;code&gt;SERVFAIL&lt;/code&gt;, retry with &lt;code&gt;+cd&lt;/code&gt; (checking disabled). If an answer appears, DNSSEC validation is failing, often because a domain moved to new nameservers while an old DS record still sits at the registrar. And &lt;code&gt;REFUSED&lt;/code&gt; from a nameserver you expected to be authoritative usually means the zone was never created, or never activated, on that provider.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ask a specific resolver, then ask the source
&lt;/h2&gt;

&lt;p&gt;By default dig uses your system's resolver. Point it elsewhere with &lt;code&gt;@&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dig example.com @1.1.1.1
dig example.com @8.8.8.8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Public resolvers are still caches. To see what the zone really says, find its authoritative nameservers and query one directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dig +short NS example.com
dig example.com @ns1.example.net +norecurse
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;; &amp;lt;&amp;lt;&amp;gt;&amp;gt; DiG 9.18.30-0ubuntu0.24.04.2-Ubuntu &amp;lt;&amp;lt;&amp;gt;&amp;gt; example.com @ns1.example.net +norecurse
; (1 server found)
;; global options: +cmd
;; Got answer:
;; -&amp;gt;&amp;gt;HEADER&amp;lt;&amp;lt;- opcode: QUERY, status: NOERROR, id: 61027
;; flags: qr aa; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;example.com.                   IN      A

;; ANSWER SECTION:
example.com.            3600    IN      A       203.0.113.10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;+short&lt;/code&gt; strips everything except the values. In the authoritative reply, look for the &lt;code&gt;aa&lt;/code&gt; flag and the full TTL. If the authoritative server already returns the new IP and a resolver still returns the old one, nothing is broken; you're waiting for cached copies to expire. If the authoritative server returns the old IP, like the one above, your change never reached the zone that's actually live. That leads to the next check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Follow the chain with +trace
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dig +trace +nodnssec example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;+trace&lt;/code&gt; skips your resolver's cache and walks the delegation itself. It asks a root server who handles &lt;code&gt;.com&lt;/code&gt;, asks a &lt;code&gt;.com&lt;/code&gt; server who handles &lt;code&gt;example.com&lt;/code&gt;, then asks that nameserver for the record. The &lt;code&gt;+nodnssec&lt;/code&gt; part just hides the signature records so the output stays readable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqsne8jh1pd1t9zwiaizk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqsne8jh1pd1t9zwiaizk.png" alt="Flat diagram of DNS resolution: a laptop asks a recursive resolver, which queries a root server, the .com TLD server and then the authoritative nameserver ns1.example.net for example.com" width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here are the last two hops, trimmed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;example.com.            172800  IN      NS      ns1.example.net.
example.com.            172800  IN      NS      ns2.example.net.
;; Received 87 bytes from 192.5.6.30#53(a.gtld-servers.net) in 24 ms

example.com.            3600    IN      A       203.0.113.10
;; Received 56 bytes from 198.51.100.53#53(ns1.example.net) in 31 ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each &lt;code&gt;Received ... from&lt;/code&gt; line names the server that sent the block above it. The hop from &lt;code&gt;a.gtld-servers.net&lt;/code&gt; is the delegation your registrar published. The final hop is what the zone itself says.&lt;/p&gt;

&lt;h2&gt;
  
  
  When the registrar and the zone disagree
&lt;/h2&gt;

&lt;p&gt;This is the classic "I changed the record and nothing happened." You moved DNS to a new provider and created the records there, but the registrar still delegates to the old nameservers. The new zone is perfect, and nobody is reading it.&lt;/p&gt;

&lt;p&gt;Ask a &lt;code&gt;.com&lt;/code&gt; TLD server directly what the delegation looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dig NS example.com @a.gtld-servers.net
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The NS records come back in the AUTHORITY section, not ANSWER, because the TLD server is referring you elsewhere rather than answering itself. That's also why &lt;code&gt;+short&lt;/code&gt; prints nothing here. Then compare with the zone you've been editing and a public resolver:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dig +short NS example.com @ns1.example.org
dig +short example.com @ns1.example.org
dig +short example.com @1.1.1.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6s40jfil6exa36x3auir.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6s40jfil6exa36x3auir.png" alt="Terminal screenshot comparing dig NS example.com at a.gtld-servers.net, which still delegates to ns1.example.net, with the new nameserver ns1.example.org returning 203.0.113.20 while 1.1.1.1 still returns 203.0.113.10" width="800" height="547"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here the TLD still points to &lt;code&gt;ns1.example.net&lt;/code&gt; and &lt;code&gt;ns2.example.net&lt;/code&gt;, while the new provider's zone lists &lt;code&gt;ns1.example.org&lt;/code&gt; and already holds the new IP. Resolvers follow the delegation, so they keep reading the old zone. The fix lives at the registrar: update the nameservers there, and remove any stale DS record if DNSSEC was enabled. Delegation records at the TLD usually carry a 172800-second TTL (two days), so resolvers that cached the old NS set can take a while to catch up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick checks for MX, TXT and CNAME
&lt;/h2&gt;

&lt;p&gt;Add the record type, and &lt;code&gt;+short&lt;/code&gt; makes each check one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;dig +short MX example.com
10 mail.example.com.
20 mail2.example.com.
&lt;span class="nv"&gt;$ &lt;/span&gt;dig +short TXT example.com
&lt;span class="s2"&gt;"v=spf1 ip4:198.51.100.25 include:_spf.example.net -all"&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;dig +noall +answer www.example.com
www.example.com.        3600    IN      CNAME   example.com.
example.com.            2917    IN      A       203.0.113.10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Worth checking while you're there:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MX records must point to hostnames, never to IP addresses or CNAMEs. The lower number has priority.&lt;/li&gt;
&lt;li&gt;A domain should publish exactly one &lt;code&gt;v=spf1&lt;/code&gt; TXT record. Two of them is an error, and receivers treat it as an SPF failure.&lt;/li&gt;
&lt;li&gt;DKIM and DMARC live on their own names: &lt;code&gt;dig +short TXT selector1._domainkey.example.com&lt;/code&gt; and &lt;code&gt;dig +short TXT _dmarc.example.com&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;+noall +answer&lt;/code&gt; shows a whole CNAME chain in one go. A name that has a CNAME can't hold any other records, which is why the bare domain usually can't be a CNAME.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Clear the cache on your own machine
&lt;/h2&gt;

&lt;p&gt;Once the authoritative servers and public resolvers agree, your own computer may still hold the old answer.&lt;/p&gt;

&lt;p&gt;On Linux with systemd-resolved:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;resolvectl flush-caches
resolvectl statistics    &lt;span class="c"&gt;# "Current Cache Size" drops to 0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On macOS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dscacheutil &lt;span class="nt"&gt;-flushcache&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;killall &lt;span class="nt"&gt;-HUP&lt;/span&gt; mDNSResponder
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Windows it's &lt;code&gt;ipconfig /flushdns&lt;/code&gt;. Browsers keep their own DNS cache, so restart the browser too. Public resolvers can't be flushed from your machine, though the big public DNS services offer a purge page for single names.&lt;/p&gt;

&lt;h2&gt;
  
  
  A short troubleshooting checklist
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;dig name type&lt;/code&gt; and read &lt;code&gt;status&lt;/code&gt; before anything else.&lt;/li&gt;
&lt;li&gt;Check the &lt;code&gt;SERVER&lt;/code&gt; line so you know whose cache you're looking at.&lt;/li&gt;
&lt;li&gt;Find the nameservers with &lt;code&gt;dig NS&lt;/code&gt;, query one with &lt;code&gt;@&lt;/code&gt;, and look for the &lt;code&gt;aa&lt;/code&gt; flag.&lt;/li&gt;
&lt;li&gt;Compare that with the TLD delegation (&lt;code&gt;dig NS name @a.gtld-servers.net&lt;/code&gt; or &lt;code&gt;+trace&lt;/code&gt;). They must match.&lt;/li&gt;
&lt;li&gt;Ask &lt;code&gt;@1.1.1.1&lt;/code&gt; and &lt;code&gt;@8.8.8.8&lt;/code&gt; and note the remaining TTL. That's how long you'll wait.&lt;/li&gt;
&lt;li&gt;On &lt;code&gt;SERVFAIL&lt;/code&gt;, retry with &lt;code&gt;+cd&lt;/code&gt; to rule DNSSEC in or out.&lt;/li&gt;
&lt;li&gt;Flush local caches only after the authoritative answer is correct.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most DNS problems aren't mysterious. They're a mismatch between two layers (registrar and zone, zone and cache, cache and browser), and the dig command shows you exactly which two. If your domains and DNS are managed at &lt;a href="https://progineous.com/?utm_source=devto" rel="noopener noreferrer"&gt;Pro Gineous&lt;/a&gt;, our support team is happy to walk through these checks with you whenever something looks off.&lt;/p&gt;

&lt;p&gt;Every domain registered through our &lt;a href="https://progineous.com/domain-search.php?utm_source=devto" rel="noopener noreferrer"&gt;domain search and registration&lt;/a&gt; page comes with DNS you can manage yourself, so you can check your records with dig as you change them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Related reading
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.progineous.com/how-long-dns-changes-take/" rel="noopener noreferrer"&gt;How Long Do DNS Changes Take to Show Up?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.progineous.com/what-an-a-record-does/" rel="noopener noreferrer"&gt;What an A Record Does in DNS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.progineous.com/how-to-set-up-dns-records/" rel="noopener noreferrer"&gt;Setting Up DNS Records for Your Website and Mail&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://blog.progineous.com/read-dig-output-dns/" rel="noopener noreferrer"&gt;Pro Gineous blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>dns</category>
      <category>linux</category>
      <category>devops</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Secure a Linux VPS in 15 Minutes</title>
      <dc:creator>Pro Gineous</dc:creator>
      <pubDate>Sat, 17 Oct 2026 05:51:50 +0000</pubDate>
      <link>https://dev.to/progineous/secure-a-linux-vps-in-15-minutes-8en</link>
      <guid>https://dev.to/progineous/secure-a-linux-vps-in-15-minutes-8en</guid>
      <description>&lt;p&gt;You just got root on a fresh Ubuntu box. That is power and risk in the same login. Attackers scan new IPs within minutes. This walkthrough hardens a typical Ubuntu 22.04 or 24.04 VPS in about a quarter of an hour. It works on any honest provider. If you use Pro Gineous, the steps are the same.&lt;/p&gt;

&lt;p&gt;Assumptions: you can SSH in as root (or with sudo), you have a second terminal ready, and you are okay restarting SSH carefully.&lt;/p&gt;

&lt;h2&gt;
  
  
  Minutes 0-3: update and create a user
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt upgrade &lt;span class="nt"&gt;-y&lt;/span&gt;
adduser deploy
usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use a real password for &lt;code&gt;deploy&lt;/code&gt; even if you plan to disable password SSH later. Keep the root session open until key login works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Minutes 3-6: SSH keys only
&lt;/h2&gt;

&lt;p&gt;On your laptop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh-copy-id deploy@YOUR_SERVER_IP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirm you can open a &lt;strong&gt;second&lt;/strong&gt; SSH session as &lt;code&gt;deploy&lt;/code&gt; before you change sshd. Then edit &lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt; (or a drop-in under &lt;code&gt;/etc/ssh/sshd_config.d/&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ssh"&gt;&lt;code&gt;&lt;span class="k"&gt;PermitRootLogin&lt;/span&gt; &lt;span class="no"&gt;no&lt;/span&gt;
&lt;span class="k"&gt;PasswordAuthentication&lt;/span&gt; &lt;span class="no"&gt;no&lt;/span&gt;
&lt;span class="k"&gt;PubkeyAuthentication&lt;/span&gt; &lt;span class="no"&gt;yes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reload SSH (name varies by release):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl reload ssh &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl reload sshd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgqk0814orakccwu5vg6g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgqk0814orakccwu5vg6g.png" alt="Terminal showing ssh-copy-id installing the key for user deploy, logging in as deploy, and sshd settings confirming PermitRootLogin no, PubkeyAuthentication yes and PasswordAuthentication no" width="800" height="467"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If the second session still works, you are safe. Only then close the root session.&lt;/p&gt;

&lt;h2&gt;
  
  
  Minutes 6-8: firewall
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ufw default deny incoming
ufw default allow outgoing
ufw allow OpenSSH
ufw allow 80/tcp
ufw allow 443/tcp
ufw &lt;span class="nb"&gt;enable
&lt;/span&gt;ufw status verbose
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx4qkb4yz86a7t4xi5sum.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx4qkb4yz86a7t4xi5sum.png" alt="Terminal showing sudo ufw enable, then sudo ufw status verbose listing the active rules: OpenSSH, 80/tcp and 443/tcp allowed inbound over IPv4 and IPv6, with default deny incoming" width="800" height="467"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you use a non-default SSH port, allow that port instead of &lt;code&gt;OpenSSH&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Minutes 8-10: fail2ban
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; fail2ban
systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; &lt;span class="nt"&gt;--now&lt;/span&gt; fail2ban
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Default jails already watch SSH on many Ubuntu images. Check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;fail2ban-client status
fail2ban-client status sshd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;sshd&lt;/code&gt; is missing, add a simple jail under &lt;code&gt;/etc/fail2ban/jail.d/sshd.local&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[sshd]&lt;/span&gt;
&lt;span class="py"&gt;enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then &lt;code&gt;systemctl restart fail2ban&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Minutes 10-12: automatic security updates
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; unattended-upgrades
dpkg-reconfigure &lt;span class="nt"&gt;-plow&lt;/span&gt; unattended-upgrades
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confirm it is active:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl status unattended-upgrades &lt;span class="nt"&gt;--no-pager&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuvu3m1jel5c8en0lqnv8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuvu3m1jel5c8en0lqnv8.png" alt="Terminal showing sudo fail2ban-client status sshd with banned IP addresses, followed by systemctl status unattended-upgrades reporting the service as active and running" width="800" height="467"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Minutes 12-14: timezone, swap, noise
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;timedatectl set-timezone UTC   &lt;span class="c"&gt;# or your region&lt;/span&gt;
ss &lt;span class="nt"&gt;-tulpn&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Close ports and services you do not need. On a 1-2 GB box with no swap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;fallocate &lt;span class="nt"&gt;-l&lt;/span&gt; 1G /swapfile
&lt;span class="nb"&gt;chmod &lt;/span&gt;600 /swapfile
mkswap /swapfile
swapon /swapfile
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'/swapfile none swap sw 0 0'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; /etc/fstab
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Skip swap if your provider already gives you enough RAM or managed swap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Minutes 14-15: snapshot and backups
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Take a provider snapshot &lt;strong&gt;now&lt;/strong&gt;, while the box is clean and hardened.&lt;/li&gt;
&lt;li&gt;Schedule off-server backups for anything that will hold real data (database dumps, &lt;code&gt;/var/www&lt;/code&gt;, secrets). A snapshot is not a tested restore.&lt;/li&gt;
&lt;li&gt;Write down where backups live and how you would restore them.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What this does not cover
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Application security (WordPress plugins, dependency CVEs, auth bugs)&lt;/li&gt;
&lt;li&gt;Secrets management (API keys in git, world-readable &lt;code&gt;.env&lt;/code&gt; files)&lt;/li&gt;
&lt;li&gt;Monitoring and alerting&lt;/li&gt;
&lt;li&gt;DDoS or network-layer attacks beyond a basic firewall&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those are next layers. This checklist only makes a fresh VPS less of an open door.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;Own the server, own the failure modes. Fifteen minutes of boring hygiene beats a weekend of incident cleanup.&lt;/p&gt;

&lt;p&gt;If you need a server to practice on, &lt;a href="https://progineous.com/?utm_source=devto&amp;amp;utm_medium=social&amp;amp;utm_campaign=week1026" rel="noopener noreferrer"&gt;Pro Gineous&lt;/a&gt; VPS plans start at $6.99/mo. Whatever provider you pick, own your domain, keep SSH locked down, and test a restore at least once.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>security</category>
      <category>devops</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
