Inbound traffic: expose less and guard what is left
Every port a machine listens on is a promise to answer a stranger and most incidents start with a promise nobody meant to make: a database bound to 0.0.0.0, an admin panel on a public IP, a forgotten service a scanner found before you did. The internet is scanned end to end continuously, so an exposed service is not a risk that might be noticed, it is one that will be, usually within hours. Ingress hardening is therefore two disciplines: expose as little as possible and put the few things that must be exposed behind controls that assume they will be attacked.
Minimum exposure is the whole game
Before any firewall rule, the question is what is listening and why. Most of what turns up should not be reachable from outside and often should not be listening on an external interface at all:
ss -tlnp # every listening TCP socket, and the process behind it
The common findings are a database on 0.0.0.0:5432, a cache (redis, memcached) with no auth answering the world, a metrics or admin endpoint on a public interface, a management port that belongs on the internal network. The first fix is not a firewall rule, it is binding the service to the interface it should answer: a database that only serves local apps binds to 127.0.0.1 or the internal address and then no firewall mistake can expose it because it is not listening where the internet can reach. Bind narrow first; firewall second.
Default-deny at the edge
The firewall posture for inbound is the uncontroversial one that is still half-implemented everywhere: deny by default, permit the specific services that are meant to be public and allow the management ones only from the networks that administer them:
iptables -P INPUT DROP
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT # the public service
iptables -A INPUT -s 10.0.10.0/24 -p tcp --dport 22 -j ACCEPT # SSH from admin net only
The detail that matters is that public and management exposure are different questions. A web server answers 443 to the world; its SSH answers only the admin range, because there is no reason for the internet to reach the administrative door at all. Collapsing those two, exposing SSH or a control panel to everyone because the web service is public, is the single most common over-exposure and it is a one-line fix.
Put web services behind a reverse proxy
A public web application should not be the thing facing the internet directly. A reverse proxy in front of it terminates TLS, presents one hardened, patchable front door and gives you the place to apply rate limits, request filtering and sane timeouts that the application itself often cannot:
server {
listen 443 ssl;
ssl_protocols TLSv1.2 TLSv1.3; # nothing older
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
limit_req zone=app burst=20 nodelay; # blunt brute force / abuse
location / { proxy_pass http://127.0.0.1:8080; } # app binds to localhost
}
The application binds to localhost, so the only way to it is through the proxy and TLS is terminated on a current protocol set with HSTS so a downgrade is not on the table. The proxy is also where a WAF (ModSecurity, or the platform’s own) lives if you run one and where request size and timeout limits turn a slow-request abuse into a dropped connection rather than an exhausted worker pool.
Brute force and the services that invite it
Anything that authenticates and faces the network gets brute-forced, SSH and any login form first among them. The real answer for SSH is keys-only (covered in its own right), which removes the password there is to guess; fail2ban on top drains the log noise and drops the obvious repeat offenders at the firewall. For web logins the equivalent is rate limiting at the proxy and lockout logic in the application. The honest framing, as with fail2ban generally, is that this reduces noise and stops the dumb automated floods; it does not stop a distributed, patient attacker and it is not a substitute for strong authentication behind the login.
DDoS, at the level you can actually handle
Volumetric floods that saturate the link cannot be solved on the host, because the packets have already consumed the bandwidth by the time they arrive; that is an upstream or scrubbing-provider problem and pretending a server-side rule fixes it is the mistake to avoid. What the host can do is survive the smaller resource-exhaustion attacks: SYN cookies against SYN floods, connection-tracking and per-source connection limits so one source cannot consume the state table and the request timeouts above so a Slowloris-style slow-drip cannot hold workers open indefinitely:
# /etc/sysctl.d/ingress.conf
net.ipv4.tcp_syncookies = 1
net.netfilter.nf_conntrack_max = 1000000
Be clear with yourself about which tier a given attack is, because the controls do not transfer: a conntrack limit does nothing against a link-saturating flood and a scrubbing contract does nothing against a slow-request attack that stays under the volume threshold.
Where to land
Start by listening less: ss -tlnp, bind every service to the narrowest interface that serves its real clients and get the databases and admin endpoints off external interfaces entirely. Then default-deny inbound, exposing public services to the world and management services only to the admin network, never collapsing the two. Put web applications behind a reverse proxy that terminates modern TLS, rate-limits and fronts a localhost-bound app. Drain brute force with keys and fail2ban while remembering it is noise reduction, not authentication. And size your DDoS expectations to the tier you can handle on the host, leaving the volumetric problem upstream where it lives. Ingress is necessary and not sufficient on its own: it is the wall and the egress controls are what catch whatever gets over it.