Vucense

Linux Networking Basics 2026: ip, ss, UFW, DNS, and Network Troubleshooting

🟢Beginner

Master Linux networking on Ubuntu 24.04. Covers ip command, ss for connections, UFW firewall, DNS resolution, network interfaces, static IPs, and systematic troubleshooting workflows.

Noah Choi

Author

Noah Choi

Linux & Cloud Native Infrastructure Engineer

Published

Duration

Reading

17 min

Build

15 min

Linux Networking Basics 2026: ip, ss, UFW, DNS, and Network Troubleshooting
Article Roadmap

Key Takeaways

  • ip not ifconfig: ip addr show replaced ifconfig. ip route show replaced route. ip link set eth0 up replaced ifconfig eth0 up. The net-tools package (which contained ifconfig, netstat) is not installed by default on Ubuntu 24.04 and is considered legacy.
  • ss not netstat: ss -tlnp shows all TCP listening ports — it’s faster and more accurate than netstat -tlnp. The -t means TCP, -l means listening, -n means numeric (no DNS reverse lookup), -p means show process.
  • UFW for humans: UFW translates readable rules (ufw allow 443/tcp) into iptables rules. Always check sudo ufw status verbose before and after changes. The default policy matters: ufw default deny incoming blocks everything not explicitly allowed.
  • systemd-resolved is the DNS layer: On Ubuntu 24.04, DNS requests go through systemd-resolved, a local stub resolver on 127.0.0.53. Use resolvectl status to inspect DNS servers, not /etc/resolv.conf directly.

Introduction

Direct Answer: What are the essential Linux networking commands for Ubuntu 24.04 in 2026?

On Ubuntu 24.04, the five most-used networking commands are: ip addr show (list network interfaces and IP addresses), ss -tlnp (list all TCP listening ports with process names), sudo ufw status verbose (show firewall rules), resolvectl status (show DNS configuration), and ip route show (show routing table and default gateway). To test connectivity: ping -c4 8.8.8.8 (ICMP to internet), curl -sI https://example.com (HTTP layer test), and traceroute example.com (path to host). For the modern Ubuntu 24.04 network stack: ifconfig and netstat are replaced by ip and ss respectively — both are part of the iproute2 package pre-installed on every Ubuntu server. /etc/netplan/ contains network configuration files for persistent settings; edit YAML there and run sudo netplan apply to apply changes without rebooting.

“Networking problems are always one of five things: wrong IP, wrong route, blocked by firewall, DNS failure, or service not listening. Know how to check each one in under 60 seconds and you can debug any network issue.”


Part 1: Network Interfaces with ip addr

# Show all network interfaces and their IP addresses
ip addr show

Expected output (annotated):

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo          ← Loopback (localhost)
    inet6 ::1/128 scope host

2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq state UP
    link/ether 96:00:02:88:0a:1c brd ff:ff:ff:ff:ff:ff     ← MAC address
    inet 5.161.88.47/32 brd 5.161.88.47 scope global dynamic eth0
    ↑ Public IPv4 address  ↑ CIDR prefix   ↑ Scope (global = routable)
    inet6 2a01:4ff:1f0:e2a8::1/64 scope global dynamic mngtmpaddr
    ↑ IPv6 address

Reading the key fields:

  • UP = interface is enabled; LOWER_UP = physical link is connected
  • inet 5.161.88.47/32 = IPv4 address with CIDR prefix (32 = single host)
  • scope global = externally routable; scope host = localhost only
# Show only IPv4 addresses (clean output)
ip -4 addr show | grep inet | awk '{print $2, $NF}'

Expected output:

127.0.0.1/8 lo
5.161.88.47/32 eth0
# Show one specific interface
ip addr show eth0

# Show interface statistics (bytes sent/received, errors)
ip -s link show eth0

Expected output of ip -s link show eth0 (key lines):

2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 ...
    RX: bytes  packets  errors  dropped
    184729012  234891   0       0           ← Received — errors:0 is healthy
    TX: bytes  packets  errors  dropped
    28341290   189234   0       0           ← Transmitted — errors:0 is healthy

Part 2: Routing Table with ip route

# Show the routing table
ip route show

Expected output:

default via 172.31.1.1 dev eth0 proto dhcp src 5.161.88.47 metric 100
172.31.1.1 dev eth0 proto dhcp scope link src 5.161.88.47 metric 100

Reading the output:

  • default via 172.31.1.1 = default gateway (all traffic not matching other routes goes here)
  • dev eth0 = goes via the eth0 interface
  • proto dhcp = this route was set by DHCP
  • metric 100 = route preference (lower = preferred)
# Show which route will be used for a specific destination
ip route get 8.8.8.8

Expected output:

8.8.8.8 via 172.31.1.1 dev eth0 src 5.161.88.47 uid 1000
    cache

Traffic to 8.8.8.8 goes via gateway 172.31.1.1 on eth0 from source IP 5.161.88.47.

# Add a static route (temporary — lost on reboot)
sudo ip route add 192.168.10.0/24 via 10.0.0.1

# Delete a route
sudo ip route del 192.168.10.0/24

# For permanent routes, use Netplan (see Part 5)

Part 3: Open Ports with ss

ss (Socket Statistics) replaced netstat. It queries the kernel directly and is significantly faster.

# Show all TCP listening ports with process info
ss -tlnp

Expected output:

State    Recv-Q  Send-Q  Local Address:Port  Peer Address:Port  Process
LISTEN   0       511     0.0.0.0:80          0.0.0.0:*          users:(("nginx",pid=1234,fd=7))
LISTEN   0       511     0.0.0.0:443         0.0.0.0:*          users:(("nginx",pid=1234,fd=8))
LISTEN   0       128     127.0.0.1:5432      0.0.0.0:*          users:(("postgres",pid=2345,fd=5))
LISTEN   0       128     127.0.0.1:6379      0.0.0.0:*          users:(("redis-server",pid=3456,fd=6))
LISTEN   0       128     127.0.0.1:11434     0.0.0.0:*          users:(("ollama",pid=4567,fd=9))
LISTEN   0       128     0.0.0.0:22          0.0.0.0:*          users:(("sshd",pid=567,fd=3))

Reading the output:

  • Local Address:Port = where the service is listening
  • 0.0.0.0:80 = listening on all interfaces (exposed to network) — intentional for Nginx
  • 127.0.0.1:5432 = listening only on localhost (PostgreSQL — correct, not internet-exposed)
  • users:(("nginx",pid=1234,fd=7)) = which process is listening
# Common ss filter flags
ss -tlnp     # TCP listening, numeric, with process (most common)
ss -ulnp     # UDP listening
ss -tlnp4    # IPv4 only
ss -tlnp6    # IPv6 only

# Show established connections to a specific port
ss -tnp state established '( dport = :443 or sport = :443 )'

# Count connections by state
ss -s

Expected output of ss -s:

Total: 45
TCP:   12 (estab 5, closed 2, orphaned 0, timewait 2)

Transport  Total  IP  IPv6
RAW        0      0   0
UDP        4      2   2
TCP        10     6   4
INET       14     8   6
FRAG       0      0   0
# Check if a specific port is in use
ss -tlnp | grep ':3000' || echo "Port 3000 is free"

Expected output:

Port 3000 is free

Part 4: UFW Firewall

UFW is Ubuntu’s default firewall interface. It writes iptables rules based on human-readable commands.

# Check UFW status
sudo ufw status verbose

Expected output (UFW enabled with typical web server rules):

Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), deny (routed)
New profiles: skip

To                         Action      From
--                         ------      ----
22/tcp                     ALLOW IN    Anywhere
80/tcp                     ALLOW IN    Anywhere
443/tcp                    ALLOW IN    Anywhere
22/tcp (v6)                ALLOW IN    Anywhere (v6)
80/tcp (v6)                ALLOW IN    Anywhere (v6)
443/tcp (v6)               ALLOW IN    Anywhere (v6)

UFW command reference:

# Enable UFW (always allow SSH first!)
sudo ufw allow ssh              # MUST DO THIS FIRST or you'll lock yourself out
sudo ufw enable

# Allow by service name
sudo ufw allow ssh              # Port 22 TCP
sudo ufw allow http             # Port 80 TCP
sudo ufw allow https            # Port 443 TCP

# Allow by port number
sudo ufw allow 3000/tcp
sudo ufw allow 5432/tcp

# Allow from specific IP only (more secure)
sudo ufw allow from 203.0.113.10 to any port 22      # SSH from one IP
sudo ufw allow from 10.0.0.0/8 to any port 5432      # PostgreSQL from private network

# Deny a specific port
sudo ufw deny 8080/tcp

# Delete a rule
sudo ufw delete allow 3000/tcp
sudo ufw delete allow from 203.0.113.10 to any port 22

# Reset all rules (⚠ removes ALL rules)
sudo ufw reset

# Show rules with line numbers (useful for deletion by number)
sudo ufw status numbered

Safe UFW setup for a web server:

# Step 1: Allow SSH before enabling (critical — don't skip)
sudo ufw allow ssh

# Step 2: Allow web traffic
sudo ufw allow http
sudo ufw allow https

# Step 3: Enable with deny-by-default for incoming
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw enable

# Step 4: Verify
sudo ufw status verbose

Expected output:

Status: active
Default: deny (incoming), allow (outgoing)

To                         Action      From
--                         ------      ----
22/tcp                     ALLOW IN    Anywhere
80/tcp                     ALLOW IN    Anywhere
443/tcp                    ALLOW IN    Anywhere

Test firewall from outside (from a different machine):

# From a different machine — test which ports are accessible
nmap -sT -p 22,80,443,3000,5432 YOUR_SERVER_IP

Expected output:

PORT     STATE    SERVICE
22/tcp   open     ssh
80/tcp   open     http
443/tcp  open     https
3000/tcp filtered ppp        ← filtered = blocked by firewall (correct)
5432/tcp filtered postgresql ← filtered = correct, not exposed

Part 5: DNS with systemd-resolved

# Show DNS configuration for all interfaces
resolvectl status

Expected output:

Global
       Protocols: -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
resolv.conf mode: stub

Link 2 (eth0)
    Current Scopes: DNS
         Protocols: +DefaultRoute +LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
Current DNS Server: 185.12.64.1
       DNS Servers: 185.12.64.1 185.12.64.2
        DNS Domain: hetzner.cloud
# Test DNS resolution
resolvectl query example.com

Expected output:

example.com: 93.184.216.34         -- link: eth0
             2606:2800:220:1:248:1893:25c8:1946

-- Information acquired via protocol DNS in 12.3ms.
-- Data is authenticated: no; Data was acquired via local or encrypted transport: no
-- Data from: network
# Flush DNS cache (useful after updating /etc/hosts or DNS records)
sudo resolvectl flush-caches
echo "DNS cache flushed"

# Check /etc/resolv.conf (it's a symlink on Ubuntu 24.04)
ls -la /etc/resolv.conf
cat /etc/resolv.conf

Expected output:

lrwxrwxrwx 1 root root 39 Feb 1 10:00 /etc/resolv.conf -> ../run/systemd/resolve/stub-resolv.conf
nameserver 127.0.0.53     ← systemd-resolved stub
options edns0 trust-ad
search hetzner.cloud

127.0.0.53 is systemd-resolved’s stub listener — all DNS queries go through it first.

Override DNS for a specific domain (split DNS):

# Add to /etc/hosts for local override (highest priority, instant)
echo "192.168.1.50  api.internal.company.com" | sudo tee -a /etc/hosts

# Test that the override works
resolvectl query api.internal.company.com

Expected output:

api.internal.company.com: 192.168.1.50
-- Information acquired via protocol DNS in 0.0ms.
-- Data from: hosts

Data from: hosts confirms the /etc/hosts override was used.


Part 6: Netplan — Persistent Network Configuration

On Ubuntu 24.04 Server, network configuration lives in /etc/netplan/. Changes here persist across reboots.

# Show current Netplan config
cat /etc/netplan/*.yaml

Example output (DHCP, typical cloud server):

network:
  version: 2
  ethernets:
    eth0:
      dhcp4: true
      dhcp6: true

Configure a static IP:

sudo tee /etc/netplan/10-static.yaml << 'EOF'
network:
  version: 2
  ethernets:
    eth0:
      dhcp4: false
      addresses:
        - 5.161.88.47/32
      routes:
        - to: default
          via: 172.31.1.1
      nameservers:
        addresses:
          - 9.9.9.9    # Quad9 (privacy-focused)
          - 1.1.1.1    # Cloudflare
EOF

# Test config before applying (dry run)
sudo netplan try --timeout 30
# If network works after 30s, press Enter to confirm; otherwise it auto-reverts

# Apply permanently
sudo netplan apply

Configure additional IP address (multi-homed):

network:
  version: 2
  ethernets:
    eth0:
      dhcp4: true
      addresses:
        - 192.168.100.1/24    # Additional static IP

Part 7: Connectivity Troubleshooting Workflow

A systematic 5-step workflow that pinpoints any network issue in under 5 minutes:

#!/bin/bash
# network_debug.sh — run this first when network isn't working

TARGET="${1:-8.8.8.8}"
echo "=== NETWORK DIAGNOSTIC — Target: $TARGET ==="
echo ""

echo "STEP 1: Interface status"
ip -4 addr show | grep -E "^[0-9]|inet" | grep -v "127.0.0.1"

echo ""
echo "STEP 2: Default gateway"
ip route show default

echo ""
echo "STEP 3: Gateway reachable?"
GW=$(ip route show default | awk '/default/ {print $3}' | head -1)
if [ -n "$GW" ]; then
    ping -c2 -W2 "$GW" > /dev/null 2>&1 && \
      echo "  ✓ Gateway $GW is reachable" || \
      echo "  ✗ Gateway $GW is NOT reachable"
else
    echo "  ✗ No default gateway configured"
fi

echo ""
echo "STEP 4: Internet connectivity (IP level)"
ping -c2 -W2 8.8.8.8 > /dev/null 2>&1 && \
  echo "  ✓ 8.8.8.8 reachable (internet working at IP level)" || \
  echo "  ✗ 8.8.8.8 NOT reachable — check firewall or gateway"

echo ""
echo "STEP 5: DNS resolution"
resolvectl query example.com > /dev/null 2>&1 && \
  echo "  ✓ DNS resolving correctly" || \
  echo "  ✗ DNS NOT resolving — check /etc/resolv.conf and systemd-resolved"

echo ""
echo "STEP 6: HTTP connectivity"
curl -sI --max-time 5 https://example.com > /dev/null 2>&1 && \
  echo "  ✓ HTTPS working end-to-end" || \
  echo "  ✗ HTTPS failing — DNS OK but TCP/TLS may be blocked"

echo ""
echo "STEP 7: Listening services"
echo "  Ports currently listening:"
ss -tlnp | awk 'NR>1 {print "    " $4 " ← " $NF}' | sort
chmod +x ~/devops-scripts/network_debug.sh
bash ~/devops-scripts/network_debug.sh google.com

Expected output (healthy server):

=== NETWORK DIAGNOSTIC — Target: google.com ===

STEP 1: Interface status
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 ...
    inet 5.161.88.47/32 brd 5.161.88.47 scope global dynamic eth0

STEP 2: Default gateway
default via 172.31.1.1 dev eth0 proto dhcp src 5.161.88.47 metric 100

STEP 3: Gateway reachable?
  ✓ Gateway 172.31.1.1 is reachable

STEP 4: Internet connectivity (IP level)
  ✓ 8.8.8.8 reachable (internet working at IP level)

STEP 5: DNS resolution
  ✓ DNS resolving correctly

STEP 6: HTTP connectivity
  ✓ HTTPS working end-to-end

STEP 7: Listening services
    0.0.0.0:22 ← users:(("sshd",pid=567))
    0.0.0.0:80 ← users:(("nginx",pid=1234))
    0.0.0.0:443 ← users:(("nginx",pid=1234))
    127.0.0.1:5432 ← users:(("postgres",pid=2345))

Quick Reference Card

# ── Interfaces ────────────────────────────────────────────────────────────
ip addr show                    # All interfaces and IPs
ip addr show eth0               # Specific interface
ip -4 addr                      # IPv4 only
ip link show                    # Interface state (UP/DOWN)
ip link set eth0 down           # Disable interface
ip link set eth0 up             # Enable interface

# ── Routing ───────────────────────────────────────────────────────────────
ip route show                   # Full routing table
ip route get 8.8.8.8            # Route for specific destination
ip route add 10.0.0.0/8 via 192.168.1.1  # Add route
ip route del 10.0.0.0/8         # Remove route

# ── Ports and Connections ─────────────────────────────────────────────────
ss -tlnp                        # TCP listening ports + process
ss -ulnp                        # UDP listening ports
ss -tnp state established       # Established TCP connections
ss -s                           # Summary statistics

# ── DNS ───────────────────────────────────────────────────────────────────
resolvectl status               # DNS config and active servers
resolvectl query example.com    # Test resolution
sudo resolvectl flush-caches    # Clear DNS cache
cat /etc/hosts                  # Local overrides (highest priority)

# ── UFW Firewall ──────────────────────────────────────────────────────────
sudo ufw status verbose         # Current rules
sudo ufw allow ssh              # Allow SSH (do before enabling)
sudo ufw allow 80/tcp           # Allow HTTP
sudo ufw allow from IP to any port 22   # Allow SSH from specific IP
sudo ufw deny 3000/tcp          # Block a port
sudo ufw delete allow 3000/tcp  # Remove a rule
sudo ufw enable                 # Activate firewall
sudo ufw disable                # Deactivate (keep rules)

# ── Connectivity Testing ──────────────────────────────────────────────────
ping -c4 8.8.8.8                # ICMP test to internet
traceroute 8.8.8.8              # Path to host (install: apt-get install traceroute)
curl -sI https://example.com    # HTTP response headers
curl -s telnet://host:port      # Test TCP port connectivity
nmap -sT -p 22,80,443 SERVER    # Scan specific ports (install: apt-get install nmap)

Troubleshooting

Network unreachable after Netplan change

Cause: Wrong gateway IP or incorrect subnet mask in Netplan config. Fix: The netplan try command auto-reverts after 30 seconds if you can’t confirm. Always use sudo netplan try instead of sudo netplan apply for risky changes. If locked out: restore from VPS console access.

Port shows LISTEN but connection refused from external

Cause: Service is listening on 127.0.0.1 (localhost only), not 0.0.0.0 (all interfaces), or UFW is blocking the port. Fix:

# Check what address the service is bound to
ss -tlnp | grep :3000
# 127.0.0.1:3000 = localhost only → change app config to bind 0.0.0.0
# 0.0.0.0:3000 = all interfaces → check UFW: sudo ufw status | grep 3000

DNS not resolving after /etc/resolv.conf edit

Cause: On Ubuntu 24.04, /etc/resolv.conf is a symlink to systemd-resolved’s stub. Direct edits are overwritten on reboot. Fix: Configure DNS via Netplan nameservers: section, or configure systemd-resolved directly:

sudo mkdir -p /etc/systemd/resolved.conf.d/
sudo tee /etc/systemd/resolved.conf.d/dns.conf << 'EOF'
[Resolve]
DNS=9.9.9.9 1.1.1.1
FallbackDNS=8.8.8.8
EOF
sudo systemctl restart systemd-resolved

Conclusion

The modern Linux networking toolkit is clear: ip for interfaces and routes, ss for open ports, ufw for firewall rules, resolvectl for DNS, and netplan for persistent configuration. All five are pre-installed on Ubuntu 24.04. The network debugging workflow in Part 7 systematically isolates any connectivity problem in under 5 minutes by testing each network layer from physical link to application.

Connect this knowledge to Ubuntu 24.04 LTS Server Setup Checklist for the full server hardening workflow, or SSH Hardening Guide to secure the most important open port on any server.


People Also Ask

Why does Ubuntu 24.04 not have ifconfig or netstat?

ifconfig and netstat are part of the net-tools package, which has been deprecated in favour of iproute2 since 2009. Ubuntu 24.04 does not install net-tools by default. The replacements — ip (for ifconfig/route) and ss (for netstat) — provide more information, support newer kernel features, and are actively maintained. If you need legacy tools for script compatibility, install them with sudo apt-get install net-tools, but new scripts should use ip and ss.

How do I check which process is using a specific port?

Use ss -tlnp | grep :PORT. For example, ss -tlnp | grep :80 shows what’s listening on port 80. The output includes the process name and PID in the users: column. Alternatively, sudo lsof -i :80 gives a more detailed view including all processes with open connections to that port, not just the listener.

What is the difference between a subnet mask and CIDR notation?

Both express the same concept — how many bits of an IP address are the network portion. CIDR /24 means the first 24 bits are the network address, equivalent to subnet mask 255.255.255.0. A /24 network has 254 usable host addresses. /32 is a single host (all 32 bits are the host address). /16 is a class B network (65,534 hosts). CIDR is the modern notation and is used throughout ip command output. The old subnet mask notation (255.255.255.0) is still used in some tools and documentation but is equivalent.


Further Reading


Tested on: Ubuntu 24.04 LTS (Hetzner CX22), Ubuntu 24.04 LTS (Raspberry Pi 5 4GB). iproute2 6.1.0, UFW 0.36.2. Last verified: April 22, 2026.

Further Reading

All Dev Corner

Comments