
Firewall: The Grumpy Gatekeeper Protecting Your Server
Why your server isn't hacked. From 'Packet Filtering' checking ports/IPs to AWS Security Groups. Evolution of Firewalls.

Why your server isn't hacked. From 'Packet Filtering' checking ports/IPs to AWS Security Groups. Evolution of Firewalls.
Why does my server crash? OS's desperate struggle to manage limited memory. War against Fragmentation.

Two ways to escape a maze. Spread out wide (BFS) or dig deep (DFS)? Who finds the shortest path?

A comprehensive deep dive into client-side storage. From Cookies to IndexedDB and the Cache API. We explore security best practices for JWT storage (XSS vs CSRF), performance implications of synchronous APIs, and how to build offline-first applications using Service Workers.

Fast by name. Partitioning around a Pivot. Why is it the standard library choice despite O(N²) worst case?

Three days after deploying my first service, I checked the AWS dashboard and found hundreds of SSH connection attempts from unknown IPs. My blood ran cold. "My server is under attack right now." I frantically looked through Security Group settings and found SSH port 22 was open to 0.0.0.0/0 (all IPs worldwide). I quickly restricted it to my home IP only, and the attack logs stopped immediately.
That's when I realized: no matter how well I write code, if the firewall is exposed, it's all useless. From then on, I accepted the firewall as a "grumpy gatekeeper" - not being annoying, but protecting my server.
When I started studying firewalls, terms poured in: Inbound, Outbound, Stateful, Stateless, Security Group, NACL, iptables, ufw, WAF... I couldn't tell if they were all different or similar. Especially with AWS having both Security Group and NACL, I was puzzled: "Why are there two firewalls?"
What confused me more was when connections failed even after setting firewall rules. I spent an hour screaming "I definitely opened port 80, why doesn't it work?" only to discover another firewall called ufw inside the server was blocking it. That's when I first learned firewalls come in multiple layers.
A firewall is a big bouncer standing at the club entrance. This analogy made everything clear for me. He doesn't let just anyone in. Only those on the list (WhiteList) or following the dress code can pass. He checks ID badges (IP addresses), which entrance you're coming through (port number), where you came from (source IP), and even what's inside your bag (packet content).
The internet world is dangerous. If you leave the door wide open, Russian hackers, Chinese bots, and DDoS attacks from your neighbor pour in 24/7. So we set up a gatekeeper called Firewall and give it rules like: "Open port 22 (SSH) only to our office IP," "Anyone can enter through port 80 (HTTP) and 443 (HTTPS)," "Reject everything else."
The most important philosophy of firewalls is "Deny by Default". Just like a club bouncer says "Nobody gets in. Only people on the list can pass," firewalls operate with "Block everything. Only what I allow can enter." This is the core of security. Create an allow list (Whitelist) and block everything else.
The opposite approach - "Allow everything and only block dangerous things" (Blacklist) - is risky. New attack methods emerge daily, and adding them to the block list one by one is too slow. That's why modern firewalls mostly use the Whitelist approach.
The most basic firewall configuration pattern (Default Policy) goes like this:
Traffic coming from outside into my server. Basically Deny All. Then make exceptions: "Open doors 80 (HTTP) and 443 (HTTPS) to show the website."
Why block by default? Because packets that arrive without being requested are mostly attacks. There's an attack called Port Scanning where hackers knock on every port from 1 to 65535 asking "Which ports are open on this server?" If unnecessary ports are open, hackers attempt to infiltrate through them.
I experienced this firsthand. I installed MySQL and port 3306 was open to 0.0.0.0/0. That night, there were thousands of login attempts from Chinese IPs - a Brute Force attack trying to guess passwords. Fortunately, my password was complex so it wasn't breached, but when I blocked 3306 in the Security Group, attacks stopped immediately.
Traffic going from my server to outside. Basically Allow All. Because my server needs to run apt update or call external APIs.
For example, a Node.js server needs to access the npm registry (registry.npmjs.org) to run npm install. A Python server needs to access PyPI (pypi.org) for pip install. If you block Outbound, the server can't communicate externally, making package installation and API calls impossible.
However, in environments where security is paramount (finance, defense), Outbound is also managed with whitelist approach. They restrict like "This server can only go out to this IP on this port." This way, even if the server is hacked, it's difficult for hackers to exfiltrate data externally.
Firewalls have evolved generation by generation. I understood this as "the process of the gatekeeper getting smarter."
The simplest form. Only looks at the packet header (address label). Doesn't look at packet content.
Inspection Items:For example, it judges: "You're entering via port 22 (SSH)? Is source our office IP? No? Get out." Simple and fast. Uses minimal CPU. That's why it's still widely used.
Limitations: Doesn't look at packet content, so can't block hacking attacks coming through port 80. For instance, if someone sends SQL Injection code disguised as normal HTTP request, packet filtering firewall just says "Port 80, OK" and lets it through.
Representative Examples:iptables (Linux)Actual iptables commands look like this:
# Allow port 22 (SSH) only from 192.168.1.100
iptables -A INPUT -p tcp --dport 22 -s 192.168.1.100 -j ACCEPT
# Deny all other port 22 traffic
iptables -A INPUT -p tcp --dport 22 -j DROP
# Allow anyone on ports 80 (HTTP) and 443 (HTTPS)
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
# Deny everything else
iptables -P INPUT DROP
This is typical of 1st generation firewalls. Looking only at IP and port to decide "pass" or "block."
Got smarter. "Remembers Connection State". This means it can judge: "Oh, this is a response to a request my server sent earlier? Come in."
Core Concept: Tracking TCP 3-Way HandshakeTCP connection consists of 3 steps:
Stateful firewalls track this process. If step 3 (ACK) packet suddenly arrives without step 1 (SYN), it blocks saying "Who are you? We never established connection." This is managed by recording in a "Connection Table".
Example: Web Browsing Scenarionaver.com.naver.com server sends response (Inbound), firewall says "This is response to my earlier request, so pass" and allows it.The advantage of this method is you don't need to manually open Inbound rules one by one. Responses to Outbound requests are automatically allowed. This is why AWS Security Group is Stateful.
Limitations: Still doesn't look at packet content. If someone sends XSS (Cross-Site Scripting) attack code disguised as normal HTTP request through port 80, Stateful firewall still says "Normal connection" and lets it through.
Representative Examples:iptables (using conntrack module)Now opens the packet and looks at content. Like a gatekeeper opening the package box to see what's inside.
Inspection Items:It judges: "Came through port 80, thought you were cool, but payload contains SQL Injection attack code like DROP TABLE users? Arrest." Most powerful but expensive and CPU-intensive.
POST /login HTTP/1.1
Host: myapp.com
Content-Type: application/json
{
"username": "admin' OR '1'='1",
"password": "anything"
}
This request is SQL Injection attack. WAF detects SQL syntax like ' (single quote) and OR in the username field and blocks it saying "This is an attack."
Another example:
GET /search?q=<script>alert('XSS')</script> HTTP/1.1
This is XSS attack. WAF sees <script> tag in URL and blocks it.
Limitations: Opening every packet creates performance burden. Especially HTTPS is encrypted, so WAF must have SSL certificate to decrypt and inspect. This is called "SSL Termination." Also, false positives are frequent - normal requests mistakenly blocked as attacks.
Theory alone is useless. Let's actually set up firewall on a server.
Most commonly used firewall on Ubuntu. It's a wrapper that makes iptables easy to use.
# Install ufw (pre-installed on Ubuntu)
sudo apt install ufw
# Check current status
sudo ufw status
# Before enabling firewall, set default policy
sudo ufw default deny incoming # Block incoming by default
sudo ufw default allow outgoing # Allow outgoing by default
# Open SSH port 22 (if you don't do this, SSH connection gets cut!)
sudo ufw allow 22/tcp
# Open HTTP(80), HTTPS(443)
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# Allow SSH only from specific IP (if my home IP is 203.0.113.50)
sudo ufw allow from 203.0.113.50 to any port 22
# Enable firewall
sudo ufw enable
# Check status (with numbers)
sudo ufw status numbered
# Delete specific rule (e.g., delete rule #3)
sudo ufw delete 3
# Disable firewall
sudo ufw disable
Actual Mistake I Made:
Once while connected via SSH, I ran ufw enable without opening port 22 first, and SSH connection got cut. Fortunately, I accessed via AWS console's Session Manager and resolved it with ufw disable. Always open SSH port first before ufw enable.
iptables is a low-level tool that sets firewall rules directly in the Linux kernel. Powerful but complex.
# Check current rules
sudo iptables -L -v -n
# Set default policy
sudo iptables -P INPUT DROP # Block incoming by default
sudo iptables -P FORWARD DROP # Block forwarding
sudo iptables -P OUTPUT ACCEPT # Allow outgoing
# Allow local loopback (localhost communication)
sudo iptables -A INPUT -i lo -j ACCEPT
# Allow established and related packets
sudo iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# Allow SSH port 22
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# Allow HTTP(80), HTTPS(443)
sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT
# Allow Ping (ICMP)
sudo iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
# Allow MySQL access only from specific IP (only 192.168.1.100 to port 3306)
sudo iptables -A INPUT -p tcp -s 192.168.1.100 --dport 3306 -j ACCEPT
# Save rules (persist after reboot)
sudo apt install iptables-persistent
sudo netfilter-persistent save
iptables Chain Concept:
Using cloud (AWS), two firewalls can be confusing. I organized this as "building entrance checkpoint (NACL) and office door (Security Group)."
Gatekeeper right in front of the server (EC2). Stateful. If incoming is allowed, outgoing is automatically allowed. Mainly managed here.
Characteristics:| Type | Protocol | Port Range | Source | Description |
|---|---|---|---|---|
| HTTP | TCP | 80 | 0.0.0.0/0 | Public web traffic |
| HTTPS | TCP | 443 | 0.0.0.0/0 | Public web traffic |
| SSH | TCP | 22 | 203.0.113.50/32 | My office IP |
| MySQL | TCP | 3306 | sg-12345678 | From web server SG |
Notice the last row. You can put another Security Group ID (sg-12345678) instead of IP in Source. It means "Allow access only from servers with this security group." Useful when allowing web server to access DB server.
0.0.0.0/0. Restrict to your IP only, or allow access only through VPN.web-server-sg, db-server-sg.Checkpoint at subnet (village entrance). Stateless. Must separately open incoming and outgoing doors.
Characteristics:| Rule # | Type | Protocol | Port Range | Source | Allow/Deny |
|---|---|---|---|---|---|
| 100 | HTTP | TCP | 80 | 0.0.0.0/0 | Allow |
| 200 | HTTPS | TCP | 443 | 0.0.0.0/0 | Allow |
| 300 | SSH | TCP | 22 | 203.0.113.0/24 | Allow |
| * | All | All | All | 0.0.0.0/0 | Deny |
Since NACL is Stateless, you must also open temporary ports (Ephemeral Ports) that clients use when sending requests to servers. Usually in the 32768-65535 range.
For example, when I send a request to port 80, the server sends response to my temporary port (e.g., 54321). Security Group is Stateful so it allows automatically, but NACL is Stateless so you must explicitly open it.
Outbound NACL rule:
Rule 100: TCP 32768-65535 → 0.0.0.0/0 Allow
This is confusing, so most people leave NACL at default (Allow All) and manage only with Security Group.
When to Use NACL:I organized which layer of the network stack different firewalls operate on.
| Firewall Type | OSI Layer | Inspection Items |
|---|---|---|
| Packet Filtering (iptables) | Layer 3-4 | IP, Port |
| Stateful (AWS SG) | Layer 3-4 | IP, Port, Connection State |
| WAF (AWS WAF) | Layer 7 | HTTP Header, Body, Cookie |
| Next-Gen Firewall (NGFW) | Layer 7 | Application, User Identity, Content |
Higher layers can see more information, but create bigger performance burden.
nginx is both a web server and reverse proxy. You can add simple firewall functionality to it.
# Allow only specific IPs (block others)
location /admin {
allow 203.0.113.50; # My office IP
allow 192.168.1.0/24; # Internal network
deny all; # Block everything else
proxy_pass http://backend;
}
# Block specific User-Agent (block bot traffic)
if ($http_user_agent ~* (bot|crawler|spider)) {
return 403;
}
# Rate Limiting (DDoS defense)
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;
server {
location /api {
limit_req zone=mylimit burst=20 nodelay;
proxy_pass http://backend;
}
}
# Block specific country IPs (using GeoIP module)
geo $block_country {
default 0;
CN 1; # China
RU 1; # Russia
}
server {
if ($block_country) {
return 403;
}
}
You can use nginx like an Application-Level Firewall this way. Especially Rate Limiting is effective for blocking DDoS attacks.
In enterprise networks, firewalls are placed in multiple layers. This is called Network Segmentation.
DMZ (Demilitarized Zone): A middle zone between external internet and internal network. Servers that need external exposure like web servers go in DMZ, while sensitive servers like DB servers go in internal network (Private Zone).
Internet
│
[Firewall 1: Edge Firewall]
│
DMZ (Web Server, API Server)
│
[Firewall 2: Internal Firewall]
│
Private Zone (DB Server, Admin Server)
Even if DMZ web server gets hacked, Firewall 2 blocks further infiltration to DB server. This is Defense in Depth strategy.
Traditional security model was Perimeter-Based Security. Assuming "internal network is safe, external is dangerous" and putting firewalls at the perimeter.
But nowadays it's shifting to Zero Trust Security. Philosophy of "Don't trust anyone, internal or external. Verify every request."
Core Principles of Zero Trust:For example, old way was "Once you enter internal network via VPN, access all resources," now it's "Even with VPN, authenticate with MFA every time, access only necessary servers."
Mistakes I made while configuring firewalls.
Most common mistake. Opening SSH (port 22) to the world makes bots attempt Brute Force attacks 24/7. Check /var/log/auth.log and you'll see thousands of login attempts from Chinese, Russian, Brazilian IPs.
allow from 203.0.113.50Thinking "open everything for now, block later" during development is risky. You'll forget later.
Solutions:nmap to check unnecessary portsNACL is Stateless so you must configure both Inbound/Outbound. Configuring only one side breaks communication.
Solutions:iptables and NACL evaluate rules in order. First matching rule applies, so order matters.
# Wrong: Block all first, then rules below are ignored
iptables -A INPUT -j DROP
iptables -A INPUT -p tcp --dport 80 -j ACCEPT # Never executes
# Correct: Write allow rules first, block at end
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -j DROP
Using Docker can mess up firewall settings. Because Docker adds its own iptables rules.
Problem:
You blocked port with ufw, but when Docker container opens that port, external access works. ufw seems to be ignored.
Cause:
Docker adds rules to iptables FORWARD chain. ufw only manages INPUT chain, so Docker's rules take priority.
// /etc/docker/daemon.json
{
"iptables": false
}
This stops Docker from touching iptables. But container networking might not work.
Solution 2: Add ufw rules to FORWARD chain too# Edit /etc/ufw/after.rules file
*filter
:DOCKER-USER - [0:0]
-A DOCKER-USER -j RETURN
COMMIT
Apply ufw rules to DOCKER-USER chain that Docker uses.
Solution 3: Bind Docker port to localhost only# Wrong: Bind to all interfaces
docker run -p 3306:3306 mysql
# Correct: Bind to localhost only
docker run -p 127.0.0.1:3306:3306 mysql
This way, access only from localhost is possible, external access impossible.
When developing, if you say "DB connection failed" or "SSH not working", 99% it's firewall issue. Don't panic, follow this checklist.
# Check firewall status
sudo /usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate
Linux:
sudo ufw status
sudo iptables -L -v -n
# Linux/Mac
traceroute example.com
# Windows
tracert example.com
You can identify where it's being blocked.
# Check with telnet
telnet example.com 80
# Check with netcat
nc -zv example.com 80
# Check with nmap (scan multiple ports)
nmap -p 22,80,443 example.com
"Connection refused" means server closed that port, "No route to host" or timeout means firewall blocked it.
# After SSH to server
sudo ufw status verbose
sudo iptables -L -v -n | grep <port number>
# Check if specific port is LISTENING
sudo netstat -tlnp | grep <port number>
# or
sudo ss -tlnp | grep <port number>
# Check if process is alive
ps aux | grep <process name>
# Check logs
sudo journalctl -u <service name> -n 50
# If Docker container
docker ps
docker logs <container name>
Sometimes it's not firewall but security modules like SELinux or AppArmor blocking.
# Check SELinux status (CentOS/RHEL)
getenforce
# Temporarily disable (re-enables after reboot)
sudo setenforce 0
# Check AppArmor status (Ubuntu)
sudo aa-status
When I first studied firewalls, I was annoyed thinking "Why so complicated?" To open one port, you configure ufw, configure Security Group, check NACL... too many steps. But after actually seeing my server under attack, I realized all these are necessary defense layers.
Firewall is a "grumpy gatekeeper," but a grateful presence protecting my server. Configure it properly even if annoying, and you can sleep peacefully at night. Without the anxiety of "Is my server being attacked right now?"
Ultimately, firewalls converge to the philosophy of "Deny by Default". Open only what's necessary, close everything else. That's the safest method.