▸ Free tool
Common Ports.
The TCP and UDP ports you actually grep for at 2am, grouped by what runs on them, with a note on each one that matters. Type in the box to filter the whole reference.
▸ Static page
Every table here is server-rendered HTML. The only script on the page is the filter box, and it never sends anything anywhere.
Showing all 105 ports.
Jump to
Badges
- Never public
- Legacy
- Local only
- Never public means an open listener on a routable address is a breach waiting to be automated. Legacy means plaintext or superseded. Local only means it has no business leaving your machine.
Nothing matches “”
The filter matches on the port number, the protocol, the service, the note, and a pile of synonyms. Try a bare number (6379), a product (kafka, vault), or a job (smtp, tracing).
Web, proxies & HTTP
Two of these carry almost all of your traffic, and the rest exist because 80 and 443 are privileged. Anything that cannot bind below 1024 — a JVM, a Node process, a container running as a non-root user — picks 8080 or 8443 and lets a proxy or a port mapping do the translation. That is the entire reason those numbers are so crowded.
The one that catches people is 443 being both TCP and UDP. HTTP/3 runs over QUIC on UDP/443, and a firewall written by somebody who has only ever thought about TCP will let the site work perfectly while quietly forcing every client back down to HTTP/2.
| Port | Proto | Service | Note |
|---|---|---|---|
| 80 | TCP | HTTP | Plaintext. Its only honest job now is a 301 to 443 — an API that still serves real responses here is handing bearer tokens to whoever owns the café Wi-Fi. |
| 443 | TCP/UDP | HTTPS · HTTP/3 (QUIC) | TLS over TCP, and QUIC over UDP for HTTP/3. When HTTP/3 silently never negotiates, it is almost always UDP/443 dropped by a firewall someone only ever wrote a TCP rule for. |
| 8080 | TCP | HTTP alternate | Tomcat, Spring Boot, Jenkins, cAdvisor, Weaviate and the Traefik dashboard all want it. The most over-subscribed number on any developer laptop. |
| 8443 | TCP | HTTPS alternate | The TLS twin of 8080. Unprivileged, so a JVM or an ingress controller can bind it without root — which is exactly why it exists. |
| 3128 | TCP | Squid forward proxy | The classic corporate egress proxy. If a container resolves DNS fine but every outbound request hangs, check whether HTTPS_PROXY should be pointing here. |
| 1080 | TCP | SOCKS proxy | SOCKS5. ssh -D 1080 user@bastion gives you one for free, and it is the cheapest way to reach a private subnet from a laptop without provisioning a VPN. |
| 2019 Local only | TCP | Caddy admin API | Loopback by default, and it must stay there: anyone who reaches it can rewrite the entire config and get certificates issued for any hostname you control. |
Databases & search
Nothing in this table belongs on a public address. Databases are the most reliably profitable thing to find open, so they are scanned constantly — and the default configuration of most of them assumes it is sitting on a trusted network, because in 1998 it was.
Two habits make the whole table safe: bind to a private interface or loopback and let the app reach it over the VPC, and reach it yourself through an SSH tunnel rather than a firewall exception you meant to remove. ssh -L 5432:127.0.0.1:5432 bastion takes three seconds and expires when you close the terminal.
| Port | Proto | Service | Note |
|---|---|---|---|
| 3306 Never public | TCP | MySQL · MariaDB | The classic protocol. Exposed to the internet it gets credential-stuffed continuously — bind it to a private address and let the app reach it inside the VPC. |
| 33060 Never public | TCP | MySQL X Protocol | MySQL 8's document protocol, enabled by default. People firewall 3306, forget this exists, and leave the same server reachable through the side door. |
| 5432 Never public | TCP | PostgreSQL | listen_addresses and pg_hba.conf are two independent gates. Opening the port without a matching pg_hba line only changes which error you get. |
| 6432 | TCP | PgBouncer | The pooler that fronts 5432. Past a few hundred clients your app should talk to this instead — and remember transaction mode breaks session state and server-side prepared statements. |
| 1433 Never public | TCP | Microsoft SQL Server | The default instance. Named instances get dynamic ports and are found through 1434/UDP, which is why so many SQL Server firewall rules are subtly wrong. |
| 1434 | UDP | SQL Server Browser | Instance discovery, and a historic amplification vector. Pin your named instances to static ports and you can switch it off entirely. |
| 1521 Never public | TCP | Oracle TNS listener | The listener, not the database. It can redirect a client to another port entirely, so one firewall rule for 1521 is frequently not enough. |
| 27017 Never public | TCP | MongoDB | Binds localhost by default since 3.6 — the ransom-note era came from builds that did not. Verify with ss rather than trusting whatever your distro packaged. |
| 27018 · 27019 | TCP | MongoDB shard · config server | Shard members and config servers in a sharded cluster. Cluster-internal traffic only; nothing outside should ever dial these. |
| 9042 | TCP | Cassandra · ScyllaDB (CQL) | The client protocol. Inter-node gossip runs on 7000, or 7001 with TLS, and it has to be open between nodes or the ring never forms. |
| 8123 · 9000 | TCP | ClickHouse HTTP · native | 9000 is the fast native protocol, and it collides with php-fpm, MinIO and Portainer. On a shared box, somebody is moving. |
| 9200 · 9300 Never public | TCP | Elasticsearch · OpenSearch | 9200 is the REST API, 9300 the inter-node transport. An open, unauthenticated 9200 is still one of the most-indexed data leaks on Shodan. |
| 7687 · 7474 | TCP | Neo4j Bolt · HTTP | Bolt is the binary driver protocol, 7474 the browser UI. Change the default password before that container lives anywhere but your laptop. |
| 6333 · 6334 | TCP | Qdrant REST · gRPC | Vector store. The API key is opt-in, so an unset key means anyone who reaches 6333 can read — and quietly rewrite — your embeddings. |
| 19530 | TCP | Milvus | The gRPC endpoint for the vector database; 9091 serves metrics and health, which collides with the Prometheus Pushgateway. |
Caches, queues & streaming
The pattern to notice here is that clustered systems need more ports than the one you configured. Redis Cluster needs its data port plus 10000. RabbitMQ needs epmd on 4369 and the Erlang distribution port alongside AMQP. Kafka needs whatever its listeners advertise, which is rarely what you typed. Half your first-cluster problems are one missing firewall rule between nodes.
The second pattern: the management interface is usually the softest part. 15672, 8222 and a Kafka UI container are all far easier to get into than the broker protocol itself, and they all hand over enough to drain a queue.
| Port | Proto | Service | Note |
|---|---|---|---|
| 6379 Never public | TCP | Redis · Valkey | The most abused open port on the internet. An unauthenticated instance is a write primitive: CONFIG SET dir, SAVE, and your authorized_keys file belongs to someone else. |
| 26379 | TCP | Redis Sentinel | Failover coordination. Clients ask Sentinel who the master is, so when this is unreachable your app fails over to nothing at all. |
| 16379 | TCP | Redis cluster bus | Always the data port plus 10000. Open 6379 between nodes but not 16379 and the cluster never finishes forming — the classic first-cluster bug. |
| 11211 Never public | TCP/UDP | Memcached | No authentication in the default build. Its UDP listener drove the 1.3 Tbps amplification attacks of 2018 and now ships disabled — check anything older than that. |
| 5672 · 5671 | TCP | RabbitMQ AMQP · AMQPS | 5671 is the TLS listener. Publisher confirms and consumer acks are what make delivery reliable — an open port only proves the socket works. |
| 15672 Never public | TCP | RabbitMQ management UI | guest/guest is restricted to loopback, but a reverse proxy on the same host makes every request look like loopback. That is how an exposed dashboard becomes an open one. |
| 15692 | TCP | RabbitMQ Prometheus metrics | From the rabbitmq_prometheus plugin. Scrape this instead of polling the management HTTP API, which gets expensive on a vhost with thousands of queues. |
| 25672 · 4369 Never public | TCP | Erlang distribution · epmd | Cluster traffic and the Erlang port mapper. A reachable epmd plus a guessable cookie is remote code execution on the node — keep both cluster-internal. |
| 9092 | TCP | Kafka broker | The advertised.listeners value matters more than the port number: clients are told where to reconnect, and inside Docker that answer is usually wrong. |
| 9093 | TCP | Kafka TLS · KRaft controller | Kafka's second listener by convention, and the KRaft controller quorum port. Also Alertmanager's default — do not put both on one host without thinking about it. |
| 2181 | TCP | ZooKeeper | Client port for pre-KRaft Kafka, HBase and friends. Peer traffic is 2888 and leader election 3888, and all three need to be open between ensemble members. |
| 4222 · 6222 · 8222 | TCP | NATS client · route · monitoring | 8222 is an unauthenticated monitoring endpoint listing connections and subjects. Useful internally, embarrassing publicly. |
| 1883 · 8883 | TCP | MQTT · MQTT over TLS | 1883 is plaintext. Mosquitto allowed anonymous publish by default for years, which is how strangers ended up toggling other people's devices. |
Containers & orchestration
This is the highest-consequence table on the page. 2375 is root on the host. 6443 is every workload in the cluster. 10250 is exec into any pod on the node. 2379 is every Secret you have. None of these has a safe failure mode, and all of them are in the standard scan list.
The other trap is the overlay network. Container-to-container traffic between hosts rides UDP — 4789 for VXLAN, 8472 for flannel and k3s — and a security group that only allows TCP produces the worst kind of outage: everything reports healthy, and nothing can talk to anything on another node.
| Port | Proto | Service | Note |
|---|---|---|---|
| 2375 Never public | TCP | Docker daemon (plaintext) | Unauthenticated root on the host, dressed up as an HTTP API. One request starts a container with the host filesystem mounted. Bots find a fresh one within minutes. |
| 2376 | TCP | Docker daemon (TLS) | The same API with mutual TLS. Correct if you genuinely need remote access — but DOCKER_HOST=ssh://host is simpler and has one fewer certificate to expire. |
| 2377 | TCP | Swarm cluster management | The manager-to-manager control plane. Open it between managers only, never to workers and never to the internet. |
| 7946 | TCP/UDP | Swarm node gossip | Both protocols, both directions. Half-open here is the classic cause of a Swarm service that reports as running but never receives a single request. |
| 4789 | UDP | VXLAN overlay data plane | The tunnel carrying container-to-container traffic between hosts. Cloud firewalls written for TCP only will break every overlay network silently. |
| 6443 | TCP | Kubernetes API server | The entire cluster behind one port. kubeadm and k3s both default here; some managed distributions front it on 443 instead. |
| 10250 Never public | TCP | kubelet API | Logs and exec for every pod on that node. Left with anonymous auth enabled it has been a full cluster takeover more than once — control plane and node-internal only. |
| 10256 · 10257 · 10259 | TCP | kube-proxy · controller-manager · scheduler | Health and metrics endpoints for the control plane components. Worth scraping from inside the cluster, never worth exposing outside it. |
| 2379 · 2380 Never public | TCP | etcd client · peer | etcd holds every Secret in the cluster, base64-encoded rather than encrypted unless you enabled encryption at rest. Read access here is total compromise. |
| 30000–32767 | TCP/UDP | Kubernetes NodePort range | Every NodePort service opens its port on every node. Fine for a demo, a liability in a cluster with public node IPs — use an ingress or a LoadBalancer. |
| 8472 | UDP | Flannel VXLAN | Flannel's tunnel port and the k3s default. Same failure mode as 4789: pod-to-pod traffic across nodes disappears without a single error message. |
| 5000 | TCP | Docker registry (distribution) | Serves plain HTTP out of the box, so clients refuse it until you add TLS or an insecure-registries entry. On macOS it also fights AirPlay Receiver. |
Observability
Almost everything in this stack ships unauthenticated, because the assumption is that it lives on a private network behind a proxy that handles identity. That assumption is load-bearing: an open Prometheus hands over your internal hostnames, your service topology and your traffic patterns, and an open Loki hands over whatever your application logs — which, if anyone was careless, includes tokens.
The numbers also collide more than anywhere else. 3000 is Grafana and every JavaScript dev server. 9093 is Alertmanager and Kafka's TLS listener. 9091 is the Pushgateway and Milvus metrics. Read the process name from ss -tulpn, not the port number.
| Port | Proto | Service | Note |
|---|---|---|---|
| 9090 Never public | TCP | Prometheus | UI and query API on one port, unauthenticated by design — it expects a proxy in front. Your metrics are an org chart and a topology map for anyone who reads them. |
| 9091 | TCP | Pushgateway | For batch jobs that die before a scrape can happen. Every other use is a mistake: it never expires series and becomes a graveyard of stale metrics. |
| 9093 | TCP | Alertmanager | Also Kafka's usual TLS listener, so read the process name before you conclude anything. Peering between Alertmanager replicas is 9094, TCP and UDP. |
| 9100 | TCP | node_exporter | Host metrics. The informal exporter range starts here and climbs: 9104 for MySQL, 9121 for Redis, 9187 for Postgres. |
| 3000 | TCP | Grafana | Shares its number with roughly every JavaScript dev server ever written. On a box that also runs a Node app, one of the two is moving to 3001. |
| 3100 | TCP | Loki | Push and query API for logs. Multi-tenancy is a header, not a port, so an open 3100 reads every tenant you have. |
| 3200 | TCP | Tempo | The query API for traces. Ingest is separate and usually OTLP on 4317 or 4318 — that is the half people forget to open. |
| 4317 · 4318 | TCP | OTLP gRPC · HTTP | The OpenTelemetry collector's two receivers. 4317 is gRPC, 4318 is HTTP/protobuf; sending one to the other gives you a socket that opens and then says nothing useful. |
| 16686 | TCP | Jaeger UI | The trace query UI. Modern deployments ingest over OTLP on 4317/4318 rather than Jaeger's own legacy collector ports. |
| 9411 | TCP | Zipkin | Still the fallback exporter in plenty of Spring and Micrometer stacks. Most collectors accept the Zipkin format here purely for compatibility. |
| 8125 | UDP | StatsD | Fire and forget. Lost packets are invisible, which is precisely the trade you accepted in exchange for never blocking the request path. |
| 24224 | TCP/UDP | Fluentd · Fluent Bit forward | The forward protocol between log agents and aggregators. Docker's fluentd log driver targets it — and blocks the container when the aggregator is down. |
| 5601 | TCP | Kibana · OpenSearch Dashboards | The window onto 9200. Locking down Kibana while leaving Elasticsearch itself open is the ELK equivalent of bolting the door and opening the window. |
| 514 Legacy | UDP | syslog | Unauthenticated, unencrypted and lossy — anyone who can reach it can forge log lines. Use 6514 for syslog over TLS when the data has to be trustworthy. |
Mail has two jobs on two different sets of ports, and mixing them up is why so much application mail disappears. Relay is server-to-server on 25, and every major cloud blocks it outbound by default. Submission is your app authenticating to a provider, and that is 587 with STARTTLS or 465 with implicit TLS.
If mail works on your laptop and silently fails on the VM, you are almost certainly submitting on 25 and being dropped by the provider's egress policy. Switch to 587 or 465, or 2525 if both are filtered — and never treat a successful TCP connect as proof that a message was accepted.
| Port | Proto | Service | Note |
|---|---|---|---|
| 25 | TCP | SMTP relay (MTA to MTA) | Server-to-server delivery. Every major cloud blocks outbound 25 by default to fight spam, which is why your app's mail silently vanished on a fresh VM. |
| 587 | TCP | SMTP submission (STARTTLS) | Where an application should submit authenticated mail. STARTTLS starts in plaintext, so the client has to require the upgrade rather than merely accept it. |
| 465 | TCP | SMTPS (implicit TLS) | Deprecated for a decade, then formally recommended again by RFC 8314. TLS from the first byte with nothing to downgrade — prefer it when the provider offers both. |
| 2525 | TCP | SMTP submission (alternate) | Not a standard, just a convention. SendGrid, Mailgun and Postmark listen here because so many networks block 25 and some block 587 too. |
| 143 · 993 | TCP | IMAP · IMAPS | 993 is implicit TLS and the only one worth enabling. 143 with STARTTLS exists mostly for clients that stopped being updated in 2009. |
| 110 · 995 Legacy | TCP | POP3 · POP3S | POP3 downloads and forgets. If something still needs it, give it 995 — plaintext 110 hands credentials to the network on every poll. |
| 1025 · 8025 Local only | TCP | Mailpit · MailHog SMTP + UI | Local mail traps for development: point the app at 1025 and read what it tried to send at 8025. Nothing leaves the machine, which is the point. |
Remote access & file transfer
One of these is fine on a public address if you configure it properly, and it is 22. The rest — RDP, VNC, SMB, Telnet, FTP — belong behind a VPN, a bastion, or an identity-aware proxy. RDP in particular is the most common initial access vector in ransomware incident reports, year after year.
SSH is also the tool that makes everything else on this page unnecessary. ssh -L 5432:127.0.0.1:5432 bastion gives you a database on localhost with no firewall change, no exception to forget about, and an audit trail. Reach for that before you open a port.
| Port | Proto | Service | Note |
|---|---|---|---|
| 22 | TCP | SSH · SFTP · SCP | Moving it to 2222 reduces log noise, not risk. Keys only, PasswordAuthentication no, and put it behind a bastion or a VPN if the host is anywhere public. |
| 3389 Never public | TCP/UDP | RDP | The most common ransomware entry point there is. This is not a rule to be casual about: gateway or VPN, never a raw public listener. |
| 5900 Never public | TCP | VNC | 5900 plus the display number, so :1 is 5901. The protocol's own password is capped at eight characters — tunnel it over SSH instead of trusting it. |
| 445 Never public | TCP | SMB | File sharing, and the port EternalBlue and WannaCry rode across the world. It should never cross a network boundary; consumer ISPs block it for good reason. |
| 139 · 137/138 Legacy | TCP/UDP | NetBIOS session · name service | Pre-SMB2 legacy. Still enabled across plenty of Windows estates, still handing hostnames and share lists to anyone on the LAN. |
| 111 Never public | TCP/UDP | rpcbind · portmapper | The directory that tells clients which random ports NFS's helper daemons landed on. Also a UDP amplification source, which makes it a scanner favourite. |
| 2049 | TCP | NFS | NFSv4 needs this port alone. v3 also needs 111 plus mountd, statd and lockd on whatever ports they happened to pick, which is why v3 firewall rules are miserable. |
| 21 · 20 Legacy | TCP | FTP control · data | Credentials in the clear, plus a second connection on a port negotiated at runtime — the reason FTP and NAT never got along. Use SFTP over 22. |
| 990 Legacy | TCP | FTPS (implicit TLS) | FTP wrapped in TLS. It still drags the two-connection design along with it; SFTP avoids the entire problem for free. |
| 23 Legacy | TCP | Telnet | A plaintext shell. If something on your network still answers here it is a switch, a printer, or an IoT device that belongs on its own VLAN. |
| 5985 · 5986 | TCP | WinRM HTTP · HTTPS | How Ansible and PowerShell Remoting drive Windows hosts. 5985 is called HTTP but the payload is usually encrypted at the protocol layer — which is not the same as TLS. |
DNS, discovery & network services
These are the services that make everything else findable, and they are almost all UDP-first, which changes how they fail. A dropped UDP packet produces a timeout and a retry, not an error — so a broken DNS or NTP path shows up as “the app is slow sometimes” rather than anything that looks like a network fault.
They are also the classic amplification sources: 53, 123, 161, 111 and 11211 all answer small questions with large responses, which is exactly what a reflection attack needs. If you run any of them on a public address, rate-limit them and disable the verbose queries.
| Port | Proto | Service | Note |
|---|---|---|---|
| 53 | TCP/UDP | DNS | UDP for most answers, TCP for anything over 512 bytes and for zone transfers. Blocking TCP/53 because “DNS is UDP” breaks DNSSEC and large answers in ways that look like random flakiness. |
| 853 | TCP | DNS over TLS (DoT) | Encrypted DNS on a port of its own, so a network operator can see it and decide. DNS over HTTPS hides inside 443 instead, which is the entire argument about it. |
| 5353 | UDP | mDNS · Bonjour | How .local names resolve with no server involved. Chatty, LAN-scoped, and the reason a discovery trick that works at the office dies in the VPC. |
| 123 | UDP | NTP | Clock sync — and clock skew is what quietly breaks TLS handshakes, JWT validation and TOTP codes. Historic amplification vector; modern configs disable the query that caused it. |
| 161 · 162 | UDP | SNMP · SNMP traps | v1 and v2c authenticate with a plaintext community string, and “public” is still live on real hardware today. Use v3, or keep it on a management VLAN. |
| 389 · 636 | TCP | LDAP · LDAPS | 389 can be upgraded with StartTLS, 636 is TLS from the start. Active Directory adds the global catalog on 3268 and 3269, which is what most integrations actually query. |
| 88 | TCP/UDP | Kerberos | Ticket granting. It is also why clock skew beyond five minutes locks an entire domain out of everything — see NTP above. |
| 67 · 68 | UDP | DHCP server · client | Broadcast, so it does not cross a router without a relay. A rogue DHCP server on a flat network is a trivial man-in-the-middle. |
| 8500 · 8600 | TCP/UDP | Consul HTTP API · DNS | 8600 answers DNS for .consul names and 8301 carries the LAN gossip. An 8500 with ACLs left disabled is read-write access to your service catalog. |
| 8200 · 8201 | TCP | Vault API · cluster | 8200 is the API and UI, 8201 replication between nodes. The unseal keys are the real perimeter here — the port is just where the conversation happens. |
| 5060 · 5061 Never public | TCP/UDP | SIP · SIP over TLS | An open 5060 gets brute-forced for toll fraud within hours. Media does not ride SIP either — RTP takes its own UDP range, typically 10000–20000. |
| 51820 | UDP | WireGuard | It never answers an unauthenticated packet, so a port scan finds nothing at all. The endpoint is effectively invisible unless you hold a key. |
| 1194 | UDP | OpenVPN | UDP by default. The TCP-on-443 fallback exists to get through hostile networks and pays for it with TCP-over-TCP meltdown the moment there is packet loss. |
Dev servers & local ports
None of these are standardised — they are conventions that stuck, and they collide constantly. 3000 is Next.js and Grafana. 5000 is Flask and a Docker registry and, on any recent Mac, AirPlay Receiver. 9000 is php-fpm and MinIO and Portainer and ClickHouse's native protocol. When a framework tells you the port is in use, it is usually telling the truth about somebody else's process.
The rule that keeps this table harmless: dev servers bind 127.0.0.1 and stay there. The moment one binds 0.0.0.0 on a café network — a Jupyter notebook, an Ollama instance, a Node process with the inspector attached — it is an unauthenticated remote shell with a friendly UI.
| Port | Proto | Service | Note |
|---|---|---|---|
| 3000 Local only | TCP | Node.js dev servers | Next.js, Express, NestJS, Rails, Create React App — and Grafana. Next increments to 3001 when 3000 is busy, which is why your OAuth callback URL suddenly stopped matching. |
| 3001 Local only | TCP | Second app or API | Where the API goes once the front end has claimed 3000. Nothing standard about it — it is just the number everyone reaches for second. |
| 4321 Local only | TCP | Astro dev server | Counts down 4-3-2-1, which is the whole joke. astro preview uses it too, so with a dev server already running preview lands on 4322. |
| 5173 Local only | TCP | Vite | Vite and everything built on it: SvelteKit, Nuxt, modern React Router. Takes 5174 next, and HMR shares the port unless you split it explicitly. |
| 4200 Local only | TCP | Angular CLI | ng serve, unchanged since the AngularJS-era tooling. One of the few dev ports you can still guess correctly on the first try. |
| 8000 Local only | TCP | Python · PHP dev servers | Django runserver, uvicorn, FastAPI, php -S, python -m http.server, Chroma. Wildly overloaded — check what is already bound before blaming your framework. |
| 8888 Never public | TCP | Jupyter | The token in the startup URL is the only authentication there is. Publishing this port without setting a password is publishing a Python shell. |
| 9000 Never public | TCP | php-fpm (FastCGI) | Not HTTP — a browser aimed at it gets nothing, because only a FastCGI client speaks the protocol. Prefer the Unix socket; an exposed 9000 is remote code execution. |
| 9229 Never public | TCP | Node.js inspector | --inspect binds loopback deliberately: the debug protocol has no auth and evaluates arbitrary code. --inspect=0.0.0.0 in a published container is a backdoor. |
| 6006 Local only | TCP | Storybook · TensorBoard | Storybook's default, and TensorBoard's as well. Both are viewers people start once and leave running for a fortnight. |
| 5000 Local only | TCP | Flask · macOS AirPlay Receiver | Flask's default, and since Monterey macOS claims 5000 and 7000 for AirPlay. The mysterious “address already in use” with nothing in ps is System Settings, not you. |
| 11434 Local only | TCP | Ollama | The local model API. It answers with no authentication, so setting OLLAMA_HOST to 0.0.0.0 on a shared network lends your GPU to the entire office. |
| 7860 · 8501 Local only | TCP | Gradio · Streamlit | The two default demo-app ports of the ML world. Gradio's share links tunnel through a public relay — understand what that means before using one at work. |
The three ranges, and why 80 needs root
A port is 16 bits, so there are 65,535 usable ones per protocol per address. IANA splits them into three ranges, and only the first one is enforced by anything.
- 0–1023, well-known. Assigned by IANA, and privileged on every Unix: binding one requires root or the CAP_NET_BIND_SERVICE capability. The rule dates to multi-user machines, where the point was stopping an ordinary user from standing up a fake telnet or SMTP daemon on the port everyone trusted.
- 1024–49151, registered. IANA keeps a list, but registration is a filing, not a reservation. Nothing stops you binding 3306 with something that is not MySQL, and plenty of software squats on numbers it never registered. Treat every entry here as a strong convention rather than a guarantee.
- 49152–65535, dynamic and private. Officially the ephemeral range — the pool the kernel picks from for the source port of an outbound connection. In practice Linux ignores IANA and defaults to 32768–60999 (net.ipv4.ip_local_port_range); Windows uses the IANA range.
The privileged-port rule has three normal escapes and you should pick one deliberately. In systemd, give the unit AmbientCapabilities=CAP_NET_BIND_SERVICE and keep running as a normal user. On Linux, lower net.ipv4.ip_unprivileged_port_start if you would rather change the policy than the process. Or do what almost everyone does: bind 8080 and let nginx, a load balancer, or a container port mapping own 80 and 443. Inside a container the question mostly disappears — the process is root in its own namespace and gets NET_BIND_SERVICE by default — but rootless Docker and Podman run into the host's rule again when they try to publish a port under 1024.
Two more facts worth having. A listening socket is unique per protocol, address and port, not per port alone: TCP/53 and UDP/53 are different sockets, and two processes can both bind 8080 on different addresses. And ephemeral exhaustion is real — a busy NAT gateway or forward proxy runs out of source ports and starts failing with “cannot assign requested address”, which reads like a bug in your code and is not.
The ports that get people owned
There is no grace period on a public IPv4 address. Mass scanners sweep the entire address space in minutes, and a handful of ports are profitable enough that they are checked first, every time. These are the ones that turn a misconfiguration into an incident.
▸ Never on a public address
- 2375 — Docker daemon. No authentication at all. A single request starts a privileged container with the host filesystem mounted, which is root. This is the fastest full compromise on the list.
- 6379 — Redis. Before Redis 6 there was no user model, and protected mode only helps when you have not overridden the bind address. The classic exploit does not even need a bug: point the dump file at an SSH authorized_keys file, write a key as a value, SAVE, log in.
- 9200 — Elasticsearch. An unauthenticated REST API over the whole index. This is where the “hacker leaves a ransom note in your database” stories come from.
- 27017 — MongoDB. Same story, same decade. Newer builds bind loopback by default, which is why the incidents thinned out rather than stopped.
- 10250 and 2379 — kubelet and etcd. Exec into any pod, or read every Secret in the cluster. Both have been the root cause of published breaches.
- 3389 — RDP. The single most common ransomware entry point. Credential stuffing does not need an exploit.
- 5060 — SIP. Brute-forced within hours for toll fraud, which bills in real money while you sleep.
The Docker publish trap nobody warns you about
Publishing a port with -p 6379:6379 binds 0.0.0.0, not localhost. Worse, Docker writes its own iptables rules into the DOCKER chain, and those are evaluated before the rules UFW manages — so ufw deny 6379 can be sitting there looking correct while the container is happily reachable from the internet. People discover this after the fact, in logs.
The fix is one prefix: -p 127.0.0.1:6379:6379 publishes to loopback only. For anything that genuinely needs to be reachable from another host, put the rule in the DOCKER-USER chain, which is evaluated first and is the officially supported hook. And if you only need it from your laptop, do not publish it at all — docker exec or an SSH tunnel gets you there without opening anything.
What to do instead
Bind to 127.0.0.1 or a private interface by default, and make the exception the thing you have to justify. Reach internal services through an SSH tunnel or a VPN rather than a firewall rule you will forget to remove. If something truly has to be public, put an authenticating proxy in front of it — not a password field bolted onto an admin UI that was never designed to face the internet. Then verify from outside: run a scan against your own public address and read the result, because your firewall config is a description of intent and the scan is the truth.
Finding what is actually listening
Every one of these answers a slightly different question, and the flags matter more than the command. -t is TCP, -u is UDP, -l is listening sockets only, -p shows the owning process, and -n skips DNS resolution so the output appears instantly instead of after a timeout. Without sudo you will see the sockets but not the process names of other users.
# Linux, the modern one. -t TCP -u UDP -l listening -p process -n numeric
ss -tulpn
ss -tulpn | grep ':5432'
# Only the sockets reachable from outside this box
ss -tulpn | grep -v '127.0.0.1' | grep -v '\[::1\]'
# macOS (and Linux) — who is holding 8080
lsof -nP -iTCP:8080 -sTCP:LISTEN
# Older hosts, or when ss is missing. Deprecated on most distros.
netstat -tulpn
# Evict whatever is squatting on 3000
fuser -k 3000/tcpRead the address column, not just the port. 127.0.0.1:5432 is loopback only. 0.0.0.0:5432 is every IPv4 interface including the public one. [::]:5432 is every IPv6 interface — and on most Linux systems that covers IPv4 too, via mapped addresses, which is a detail that has surprised a lot of people who thought they were bound to v6 only.
# What a container actually published, and on which interface
docker port my-api
docker ps --format 'table {{.Names}}\t{{.Ports}}'
# 0.0.0.0:6379->6379/tcp the whole internet
# 127.0.0.1:6379->6379/tcp loopback only <- what you almost always want
# The app binds inside the container, so check there too
docker exec my-api ss -tulpn
# Kubernetes: what the service claims vs what the pod listens on
kubectl get svc -A
kubectl exec deploy/api -- ss -tulpn
# Windows
netstat -ano | findstr :3000
Get-NetTCPConnection -LocalPort 3000 -State ListenThe container case has its own classic failure: the port mapping is correct, the container is running, and connections still fail — because the process inside bound 127.0.0.1 instead of 0.0.0.0. Inside a container, loopback means the container's own loopback, so the mapping has nothing to forward to. Frameworks that default to localhost in development (Flask, Rails, plenty of Node setups) need an explicit host flag once containerised.
# Can I reach it at all? Fast, no payload sent.
nc -vz db.internal 5432
# UDP: silence proves nothing. A closed port answers with ICMP
# that the firewall in between probably swallowed.
nc -vzu ntp.internal 123
# Refused vs timed out, with a timeout you control
curl -v --connect-timeout 3 telnet://db.internal:5432
# Where does the packet actually die
sudo traceroute -T -p 443 api.example.comRefused vs timed out — the free diagnosis
Two failures that look identical in an application log tell you completely different things, and the difference is free. It is the single most useful five seconds in network debugging: one says the packets are arriving and the service is not there, the other says the packets are not arriving at all.
| Connection refused | Connection timed out | |
|---|---|---|
| What came back | A TCP RST. Something answered you. | Nothing at all. The SYN was swallowed. |
| How long it took | Instant — one round trip. | Seconds, until your connect timeout fires. |
| What it proves | Routing, DNS and the firewall path are fine. No process is listening on that port, or a rule is REJECTing it. | Packets are being dropped: security group, DROP rule, wrong address, or no route to the host. |
| Check first | Is the service up? ss -tulpn. Is it bound to 127.0.0.1 rather than 0.0.0.0? | Security group and firewall rules, then the path: sudo traceroute -T -p PORT host. |
| Classic cause | Container publishes the port; the app inside binds loopback. | AWS security group missing the inbound rule, or you are on the wrong VPN. |
There are two more states worth naming. Connects, then hangs means the TCP handshake worked and the protocols disagree — you spoke HTTP to php-fpm on 9000, or plaintext to a TLS-only listener, and both sides are now waiting for the other to say something. Connection reset by peer mid-stream means it worked and then stopped: a proxy timeout, a TLS version the server rejected after the handshake started, or the process dying under you.
UDP does not play this game. A closed UDP port is supposed to answer with an ICMP port-unreachable, but firewalls drop ICMP as a matter of habit, so silence means either “nothing is there” or “it is fine and the service simply had nothing to say”. Tools like nc -vzu are guessing. Test UDP services with a real client — dig @host for DNS, ntpdate -q for NTP — not a port probe.
And if the error is “could not resolve host”, stop looking at ports entirely. That is DNS, it failed before a packet was ever addressed, and no firewall rule you write will change it.
Questions people ask
What port does PostgreSQL use?
5432/TCP, and MySQL or MariaDB use 3306/TCP. Those are defaults rather than requirements — a PgBouncer pooler usually answers on 6432, and any of them can be moved in config. If the port is open but the connection is still rejected, that is Postgres working correctly: listen_addresses and pg_hba.conf are separate gates and both have to allow you.
Why do I need root to bind to port 80?
On Unix, ports below 1024 are privileged: only root, or a process holding the CAP_NET_BIND_SERVICE capability, is allowed to bind them. The rule exists so that any user on a shared machine cannot impersonate a system service. You do not have to run as root to get there — grant AmbientCapabilities=CAP_NET_BIND_SERVICE in the systemd unit, lower net.ipv4.ip_unprivileged_port_start on Linux, or bind 8080 and let a reverse proxy or a port mapping own 80.
How do I find out which process is using a port?
On Linux, ss -tulpn lists every listening socket with its process, though you need sudo to see processes you do not own. On macOS the equivalent is lsof -nP -iTCP:8080 -sTCP:LISTEN. On Windows, netstat -ano | findstr :3000 gives you a PID to look up with tasklist. If nothing shows up, the listener is probably inside a container — docker port tells you what was actually published and on which interface.
What is the difference between connection refused and connection timed out?
Refused means a TCP RST came back: your packet reached the host and either nothing was listening on that port or a firewall explicitly rejected it. Timed out means nothing came back at all, so packets are being dropped somewhere — a security group, a DROP rule, the wrong IP, or a missing route. Refused points at the service, timed out points at the network in between.
Is it safe to expose Redis or the Docker socket to the internet?
No, and both are scanned continuously. An open Docker API on 2375 is unauthenticated root on the host: one request can start a container with the filesystem mounted. An open Redis on 6379 can be told to write its dump file into an SSH authorized_keys file. Bind them to 127.0.0.1 or a private interface and reach them through an SSH tunnel or a VPN instead.
What is the ephemeral port range?
It is the pool the kernel draws from for the source port of outbound connections. IANA suggests 49152–65535, Linux defaults to 32768–60999 via net.ipv4.ip_local_port_range, and Windows uses the IANA range. It starts to matter when a busy proxy or NAT gateway exhausts the pool and connections fail with “cannot assign requested address” — the fixes are widening the range, reusing TIME_WAIT sockets, or spreading traffic over more source addresses.
Related
-
Docker vs Kubernetes
Where 2375, 6443, 10250 and 2379 come from — and which of them you are actually responsible for.
-
Container vs pod
A pod shares one network namespace, which is why two containers inside it cannot both bind 8080.
-
DNS vs service discovery
Port 53 gives you a name. Service discovery gives you a name, a port, and whether the thing is healthy.
-
Forward vs reverse proxy
3128 or 443: which way the proxy faces changes every firewall rule you write around it.
-
HTTP Status Codes
Once the port answers, this is the other half of the debugging session.
-
HTTP Headers Reference
What the request and response actually carry over 80 and 443.
Keep reading
-
▸ Tool
HTTP Status Codes
The other half of the debugging lookup: what the response code is telling you.
-
▸ Post
/blog/docker-vs-kubernetes/
Port mapping stops being trivial the moment an orchestrator owns the network.
-
▸ Post
/blog/dns-vs-service-discovery/
Ports are only half the address. This is how services find each other's host.
-
▸ Tool
Chmod Calculator
Same category of thing you look up mid-incident and never memorise.