Easy way of opening and closing ports on iptables
A very common struggle I have in Linux server environments is the usage of iptables. It is a very powerful and performance efficient tool to configure network traffic but it is also a hard tool to use. This post you will find a script to manage your iptables configuration with a similar simplify as the ufw used to be.
A little bit of context
I have free-tier instances on the Oracle Cloud and looks likes they deliberated decide to replace whatever firewall toolset from the distros by iptables directly. Even from their Best Practices documentation they state the following:
Do not use Uncomplicated Firewall (UFW) to edit firewall rules on an Ubuntu image. Using UFW to edit rules might cause an instance not to boot. Therefore, we recommend you edit UFW rules using the method described in this note: Ubuntu instance fails to reboot after enabling Uncomplicated Firewall (UFW).
Since I'm not that great remembering where to add my configurations, I decided to automate the solution for my own usage.
The script
Simply copy this to a firewall.sh, run chmod +x ./firewall.sh to turn it into an executable script and have fun.
IMPORTANT: most of this script is AI generated. I reviewed and fixed it.
Quick guide:
sudo ./firewall.sh enable 8080 # Open port 8080 for both TCP and UDP
sudo ./firewall.sh enable 8080/tcp # Open port 8080 for TCP only
sudo ./firewall.sh enable 8080/udp # Open port 8080 for UDP only
sudo ./firewall.sh disable 8080 # Close port 8080 for both TCP and UDP
sudo ./firewall.sh disable 8080/tcp # Close port 8080 for TCP only#!/bin/bash
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Function to display help
show_help() {
cat << EOF
Usage: $(basename "$0") <enable|disable> <port>[/protocol]
Manage iptables firewall rules and persist them across reboots.
Commands:
enable Open the specified port
disable Close the specified port
Port format:
<port> Port number only (opens both TCP and UDP)
<port>/tcp Port number with TCP protocol
<port>/udp Port number with UDP protocol
Examples:
$(basename "$0") enable 8080 # Open port 8080 for both TCP and UDP
$(basename "$0") enable 8080/tcp # Open port 8080 for TCP only
$(basename "$0") enable 8080/udp # Open port 8080 for UDP only
$(basename "$0") disable 8080 # Close port 8080 for both TCP and UDP
$(basename "$0") disable 8080/tcp # Close port 8080 for TCP only
Note: This script requires root privileges.
EOF
}
# Function to print colored messages
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Check if running as root
check_root() {
if [[ $EUID -ne 0 ]]; then
log_error "This script must be run as root (use sudo)"
exit 1
fi
}
# Find the position of the REJECT rule in INPUT chain
find_reject_position() {
local reject_line=$(iptables -L INPUT --line-numbers -n | grep -E "REJECT.*reject-with" | head -1 | awk '{print $1}')
if [[ -z "$reject_line" ]]; then
log_warning "No REJECT rule found in INPUT chain. Will append to the end."
echo "append"
else
echo "$reject_line"
fi
}
# Check if a rule already exists
rule_exists() {
local port=$1
local protocol=$2
iptables -C INPUT -m state --state NEW -p "$protocol" --dport "$port" -j ACCEPT 2>/dev/null
return $?
}
# Enable a port
enable_port() {
local port=$1
local protocol=$2
if rule_exists "$port" "$protocol"; then
log_warning "Port $port/$protocol is already open"
return 0
fi
local reject_pos=$(find_reject_position)
if [[ "$reject_pos" == "append" ]]; then
log_info "Adding rule for port $port/$protocol at the end of INPUT chain"
iptables -A INPUT -m state --state NEW -p "$protocol" --dport "$port" -j ACCEPT
else
log_info "Found REJECT rule at position $reject_pos"
log_info "Inserting rule for port $port/$protocol at position $reject_pos (before REJECT)"
iptables -I INPUT "$reject_pos" -m state --state NEW -p "$protocol" --dport "$port" -j ACCEPT
fi
log_success "Port $port/$protocol opened successfully"
}
# Disable a port
disable_port() {
local port=$1
local protocol=$2
if ! rule_exists "$port" "$protocol"; then
log_warning "Port $port/$protocol is not currently open"
return 0
fi
log_info "Removing rule for port $port/$protocol"
iptables -D INPUT -m state --state NEW -p "$protocol" --dport "$port" -j ACCEPT
log_success "Port $port/$protocol closed successfully"
}
# Save iptables rules
save_rules() {
log_info "Saving iptables rules to persist across reboots..."
if command -v netfilter-persistent &> /dev/null; then
netfilter-persistent save
log_success "Rules saved with netfilter-persistent"
elif command -v iptables-save &> /dev/null; then
iptables-save > /etc/iptables/rules.v4
log_success "Rules saved to /etc/iptables/rules.v4"
else
log_error "Could not find netfilter-persistent or iptables-save"
log_error "Rules will not persist across reboots!"
return 1
fi
}
# Parse port and protocol
parse_port_spec() {
local spec=$1
if [[ "$spec" =~ ^([0-9]+)(/tcp|/udp)?$ ]]; then
local port="${BASH_REMATCH[1]}"
local proto="${BASH_REMATCH[2]}"
# Validate port range
if [[ $port -lt 1 || $port -gt 65535 ]]; then
log_error "Invalid port number: $port (must be 1-65535)"
exit 1
fi
if [[ -z "$proto" ]]; then
echo "$port both"
else
echo "$port ${proto:1}" # Remove leading slash
fi
else
log_error "Invalid port specification: $spec"
show_help
exit 1
fi
}
# Main logic
main() {
if [[ $# -lt 2 ]]; then
show_help
exit 1
fi
check_root
local action=$1
local port_spec=$2
read -r port protocol <<< $(parse_port_spec "$port_spec")
case "$action" in
enable)
if [[ "$protocol" == "both" ]]; then
enable_port "$port" "tcp"
enable_port "$port" "udp"
else
enable_port "$port" "$protocol"
fi
save_rules
;;
disable)
if [[ "$protocol" == "both" ]]; then
disable_port "$port" "tcp"
disable_port "$port" "udp"
else
disable_port "$port" "$protocol"
fi
save_rules
;;
*)
log_error "Invalid action: $action"
show_help
exit 1
;;
esac
echo ""
log_info "Current iptables INPUT chain rules:"
iptables -L INPUT -n --line-numbers | grep -E "^(Chain|num|[0-9])" | head -20
}
main "$@"firewall.sh