Easy way of opening and closing ports on iptables

Jan 17, 2026

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

References

Luiz Costa

I am a senior software engineer at Red Hat / Ansible. I love automation tools, games, and coffee. I am also an active contributor to open-source projects.