List what is listening
# Listening TCP and UDP sockets, numeric, with the owning process
sudo ss -tulpnEach row shows the protocol, the state (LISTEN for TCP, UNCONN for UDP), the local address and port, and the owning process, as users:(("nginx",pid=812,fd=6)). Without sudo the process column stays empty for anything you do not own.
The local address decides who can connect. 127.0.0.1 or [::1] is reachable only from the server itself. 0.0.0.0 means every IPv4 interface; [::] or * means every IPv6 interface, and often IPv4 as well. A database or cache on 0.0.0.0 is a finding even if nothing is wrong yet, because the firewall is then the only thing between it and the internet.
What normal looks like
- sshd on 22, or the port you moved it to.
- A web server (nginx, apache2, httpd, caddy) on 80 and 443.
- Databases and caches on loopback: MySQL or MariaDB on 127.0.0.1:3306, PostgreSQL on 5432, Redis on 6379, PHP-FPM on 9000 or a Unix socket.
- systemd-resolved on 127.0.0.53:53 (and 127.0.0.54:53 on newer versions); chronyd on UDP 323, its local control port; a DHCP client on UDP 68 on cloud images.
- Control panels: cPanel on 2082, 2083, 2086, 2087, 2095 and 2096; Plesk on 8443 and 8880; DirectAdmin on 2222; plus the mail (25, 465, 587, 110, 143, 993, 995), DNS (53) and FTP (21) services they run.
- docker-proxy on ports you published with docker run -p or Compose, and metrics exporters you installed, such as node_exporter on 9100. SecAI's agent opens no listening port.
Listener red flags
- A port you cannot attribute to anything you installed, especially a high port on 0.0.0.0.
- A listener owned by www-data, apache or nginx on a port other than the web server's own. Web applications do not normally open their own listeners; a bind shell does.
- A shell or tool (sh, bash, nc, socat) owning a listening socket, or an interpreter (python, perl, php) doing so when it is not your application server.
- A listener whose binary is in /tmp, /var/tmp, /dev/shm or a hidden directory.
- Redis, Elasticsearch, a database, the Docker API on 2375, or another admin interface on a public address.
List established connections
# Established TCP connections with the owning process
sudo ss -tnp state established
# Connected UDP sockets
sudo ss -unp
# The local port range used for outgoing connections
cat /proc/sys/net/ipv4/ip_local_port_rangeWith a state filter ss drops the State column, so each row is the queues, local address and port, peer address and port, and process. Inbound connections have your service port on the local side (22, 443). Outbound ones have a local port in the ephemeral range (by default 32768 to 60999) and the service port on the peer side, such as 443 or 3306.
Normal outbound traffic: package mirrors, your own APIs, payment and mail providers, DNS to your resolver, time servers, backup destinations, and monitoring agents reporting home. SecAI's agent connects to secai.techsteps.ae on 443 and, only after three consecutive failures to reach it, opens and closes a connection to 1.1.1.1 or 9.9.9.9 on 443 to tell a SecAI outage from a dead link.
Outbound red flags
- A shell (bash, sh, dash) or a tool such as nc, ncat or socat holding an established connection: this is what a reverse shell looks like. An interpreter doing the same deserves the same suspicion unless it is your application.
- A process running as www-data, apache or nginx connecting to an address that is not one of your application's dependencies.
- Long-lived connections to ports used by mining pools, such as 3333, 4444, 5555, 7777 or 14444, or to IRC on 6667, which older botnets use for control. Ports alone prove nothing: pools also listen on 80 and 443, and legitimate software uses all of these numbers. The process and the destination together are what matter.
- Many connections from your server to many addresses on 22 or 25: it may be scanning or sending spam for someone else.
- A connection whose process binary is deleted or lives in a temp directory, or a steady connection to one address nobody can explain.
Map a socket to its process and binary
The name in ss is the short command name, which a process can fake. Take the PID from users:(...) and check what it really is.
# Everything one process has open on the network (the trailing comma stops 123 matching 1234)
sudo ss -tunap | grep "pid=<PID>,"
# The binary behind it and its full command line
sudo ls -l /proc/<PID>/exe
sudo cat /proc/<PID>/cmdline | tr "\0" " "; echo
# Which systemd unit started it
systemctl status <PID> --no-pagerIf the binary, command line or unit surprises you, the suspicious process guide takes it from there.
Docker and containers
Docker changes two things. A published port is handled by Docker's own NAT rules, which Docker documents as applying before ufw's, so a port can be reachable from the internet while ufw says it is denied. And a connection made inside a container lives in the container's network namespace, so unless the container uses host networking it does not appear in the host's ss output.
# Published ports and the containers behind them
sudo docker ps --format "table {{.Names}}\t{{.Ports}}"
# Connections inside one container's network namespace (use the PID of any process in it)
sudo nsenter -t <PID> -n ss -tunpCompare with the firewall and the view from outside
What listens is not the same as what is reachable. Read the rules, then check from another machine.
# ufw (Ubuntu, Debian)
sudo ufw status verbose
# firewalld (Rocky Linux, AlmaLinux)
sudo firewall-cmd --list-all
# The underlying rules, whatever manages them
sudo nft list ruleset
# Rules added beside ufw or firewalld, where the iptables command is installed
sudo iptables -S
# From another machine, only against a server you are responsible for (nmap may need installing)
nmap -Pn -p- <server-public-ip>Every listener on a public address should be allowed on purpose or blocked. A port that nmap finds open but you believed was firewalled is the most useful result here. A provider firewall or security group filters traffic before it reaches the server, so read those rules too; the scan from outside is the truth about exposure. Check your provider's policy before scanning.
If SecAI blocks addresses on this server, its rules sit in the tool the server already runs (fail2ban, CrowdSec, CSF, firewalld or iptables); on a server managed with ufw they show in iptables -S rather than in ufw status. Those entries are expected.
Find out who a destination is
Look addresses up from your own workstation, not the suspect server, whose resolver and tools you cannot fully trust. Reverse DNS, the network owner in whois, and vendors' published address ranges identify most legitimate destinations. dig and whois may need installing.
# On your workstation: reverse DNS and network owner of an address
dig -x <address> +short
whois <address>
# On the server: the resolver it uses and static entries that could redirect names
cat /etc/resolv.conf /etc/hostsA missing reverse name, or a hosting company's generic one, is common and not suspicious by itself. Many services sit behind content delivery networks, so an address owned by one tells you little; the process making the connection tells you more. An /etc/hosts line pointing a well-known name at an unexpected address is worth recording.
What a snapshot cannot show you
ss shows one moment. A process that connects for two seconds every ten minutes, or only at night, will usually not be in it. Run the established-connections command several times, and use your provider's flow logs or bandwidth graphs if it offers them: they are recorded outside the server and cannot be edited from it.
A rootkit can also hide sockets from ss. An advanced cross-check compares the kernel's socket tables with what ss reports. The counts drift between the two commands, so look for a persistent gap, not a difference of one or two. A kernel-level rootkit can falsify both, which is why records kept off the server matter.
# Advanced: TCP sockets in the kernel tables versus what ss reports (expect small drift)
cat /proc/net/tcp /proc/net/tcp6 | grep -vc local_address
ss -tanH | wc -lWhat to record, and when to escalate
- Record every unexpected listener and connection: local and peer address and port, PID, process, user, binary path, the command you ran and the UTC time. Copy it off the server.
- Do not kill processes, change firewall rules on the server or reboot yet. If data is leaving right now, cut the connection at the provider firewall or security group, which stops the damage and keeps the evidence.
- Escalate or plan a rebuild if a shell or interpreter holds a connection to an unknown address, anything runs from a temp directory, a socket is hidden from ss, or customer data could have left the server.
What SecAI reports about ports and connections
SecAI alerts on an outbound connection to an address the server has not spoken to before, with the port and the owning process. Its posture checks report listening sockets reachable from the internet, along with other exposure such as open mail relays and open DNS recursion, and propose a hardening fix where one exists, which waits for your approval. A connection the process table cannot account for is reported as a hidden network connection.
All three are alerts for a person to investigate; none of them closes a connection. SecAI is software, not an incident response service.