A database on a Linux server is running and `ss -tlnp` shows it in LISTEN state on port 5432, yet remote clients get no connection while a client on the same host connects fine. What in the ss Local Address:Port column explains this, and how do you fix it?
answer
- read the Local Address, not just the port
- loopback versus wildcard
- the fix is in the app config
- widening is a security decision
basics
~10 sThe Local Address is almost certainly 127.0.0.1, so the socket is bound to loopback only and is unreachable from any other host. Fix it in the application's own bind configuration, not in the firewall.
solid answer
~40 sI'd read the Local Address column: `127.0.0.1:5432` means the socket is bound to the loopback interface, so only processes on that host can reach it — packets from outside never match that socket, and the client sees a refusal or timeout. `0.0.0.0:5432` means all IPv4 addresses, `[::]:5432` all IPv6, and a single address like `10.0.1.7:5432` means that interface only. The fix is in the application's configuration, not in the firewall: PostgreSQL's `listen_addresses` in `postgresql.conf`, MySQL's `bind-address`, Redis' `bind` directive. Restart the service and confirm the Local Address changed. I'd bind to a specific internal address rather than blanket `0.0.0.0` for something like a database, and only after that would I go look at firewall rules — because if the bind address is wrong, no firewall change will ever help.
code
bash · 9 lines# Read the bind address, not just the port
sudo ss -tlnp 'sport = :5432'
# tcp LISTEN 0 244 127.0.0.1:5432 0.0.0.0:* users:(("postgres",pid=901,fd=7))
# After widening the bind in the application's own config and restarting,
# confirm the change is real:
sudo systemctl restart postgresql
sudo ss -tlnp 'sport = :5432'
# tcp LISTEN 0 244 10.0.1.7:5432 0.0.0.0:* users:(("postgres",pid=2210,fd=7))go deeper
Recognise that a listening socket has an address as well as a port, and that 127.0.0.1 means local-only. Say plainly that you would read the Local Address column before touching anything else.
Explain the four address forms you see there — loopback, IPv4 wildcard, IPv6 wildcard, specific interface — and name where the bind address is configured for a real service, then verify the change in ss output after a restart.
Show the elimination order: bind, then host firewall, then path. Treat widening a bind as an exposure decision, preferring a specific private address plus source restrictions over a blanket wildcard.
Own the default: whether services in your estate bind narrowly by policy, how that is enforced in base images and configuration management, and how you avoid a single misconfiguration exposing a datastore to a public interface.
## The column that answers the question Every listening socket is bound to an address as well as a port, and `ss` prints both in the Local Address:Port column. That single field decides who can ever reach the service: ```bash sudo ss -tlnp # tcp LISTEN 0 244 127.0.0.1:5432 0.0.0.0:* users:(("postgres",pid=901,fd=7)) # tcp LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=1234,fd=6)) # tcp LISTEN 0 128 [::]:22 [::]:* users:(("sshd",pid=812,fd=4)) ``` - **`127.0.0.1`** — bound to the loopback interface. Only processes on this host can connect. Traffic arriving on a real interface can never match this socket, no matter how correct the routing and the firewall are. - **`0.0.0.0`** — the IPv4 wildcard: every IPv4 address the host has now or acquires later. - **`[::]`** — the IPv6 wildcard. On most Linux systems, with `net.ipv6.bindv6only` at its default of 0, a socket bound to `[::]` also accepts IPv4 connections via IPv4-mapped addresses, which is why many services show only a `[::]` row and are nonetheless reachable over IPv4. - **A specific address such as `10.0.1.7`** — that interface only. Useful and deliberate: a database that should be reachable on the private network but not on the public one. So the symptom in the question — local client fine, remote client not — is the signature of a loopback bind, and `ss` shows it in one line. ## Why this is the right thing to check first The three things that can stop a remote connection are, in order of how cheaply you can rule them out: the socket is not bound where you think, a firewall drops or rejects the packet, or routing never gets the packet to the host. Reading the Local Address column costs one command on the box you are already on, and it is definitive. If it says `127.0.0.1`, no amount of firewall work will help, and hours get burned by teams that start at the firewall. The reason loopback binds are so common is that they are frequently the *default*. Many packaged services ship bound to loopback deliberately, on the reasoning that a fresh installation should not be exposed before someone has configured authentication. The Debian PostgreSQL package, MySQL's `bind-address = 127.0.0.1`, and Redis' `bind 127.0.0.1` are all examples of that stance. ## Fixing it in the right place The bind address is a property the application chooses when it calls `bind()`, so it is configured in the application, not in the kernel or the firewall: - PostgreSQL: `listen_addresses` in `postgresql.conf` (`'localhost'` by default; `'*'` or an explicit address list to widen it). - MySQL/MariaDB: `bind-address` in the server config. - Redis: the `bind` directive in `redis.conf`. - Application servers usually take a host argument or environment variable; a lot of frameworks default to binding loopback in development mode. After changing it, restart the service and re-run `ss -tlnp` — the change is only real when the Local Address column says so. A config file edited but not reloaded is the second most common version of this incident. ## The judgment part Widening the bind is a security decision, not a formality. Going from `127.0.0.1` to `0.0.0.0` exposes the service on **every** interface the host has, including any public one, and it does so the moment the process restarts. For a datastore, the better move is usually to bind the specific private address, and to layer on host firewall rules restricting the source range, so that neither control alone is the whole defence. If the traffic is genuinely only administrative, an SSH tunnel or a bastion keeps the service on loopback entirely. There is also a dual-stack failure mode worth knowing: a service that binds only `[::]` on a host where `net.ipv6.bindv6only` has been set to 1 will not accept IPv4 connections at all, and `ss` shows exactly one `[::]` LISTEN row with no `0.0.0.0` counterpart. Conversely, some services open two sockets, one per family, and you will see two rows — that is normal, not a duplicate listener. ## What to say when the bind address is fine If the Local Address is already `0.0.0.0` and remote clients still fail, you have cleanly eliminated one whole class of cause, and the next steps are the firewall on this host, then anything filtering in between, then routing. Being able to say *why* you have eliminated it — the socket accepts packets addressed to any local address — is what separates a methodical answer from guessing.
- The Local Address already reads `0.0.0.0:5432` and remote clients still cannot connect. What is your next step and why?The bind is eliminated, so the packet is being stopped or never arriving. I'd check the host firewall next since it is local and cheap to inspect, distinguishing a fast "connection refused" (a reject, or nothing bound) from a hang (a silent drop). After that, anything filtering between the client and the host, then routing. The point is that each step rules out a class of cause rather than guessing.
- A service shows only a `[::]:8080` LISTEN row and no `0.0.0.0:8080` row, but IPv4 clients connect fine. Is that expected?Yes. With `net.ipv6.bindv6only` at its default of 0, a socket bound to the IPv6 wildcard also serves IPv4 clients through IPv4-mapped addresses, so one row covers both families. If that sysctl is set to 1, the same configuration stops accepting IPv4 entirely — which is why the single row is worth recognising rather than treating as a missing listener.
- Why is binding a database to `0.0.0.0` a worse habit than binding it to a specific private address?`0.0.0.0` binds every interface the host has, including any public one, and it re-applies automatically to interfaces added later. Binding the specific private address expresses the intent in the one place that cannot be forgotten, and keeps the service off the public interface even if a firewall rule is dropped or the host is re-homed.
saying these in an interview costs you the question
- Blaming the firewall before reading the bind address
- Thinking 127.0.0.1 and 0.0.0.0 are interchangeable
- Editing config without restarting, then declaring it fixed
- Binding everything to 0.0.0.0 as the standard fix
- Assuming a missing 0.0.0.0 row means IPv4 is unreachable