Introduction
SCADA (Supervisory Control and Data Acquisition) systems control critical infrastructure like power grids, chemical plants, and water treatment facilities. In 2026, with the rise of IIoT and sophisticated state-sponsored attacks (remember Stuxnet or the Colonial Pipeline incident), securing them is no longer optional—it's vital. A single breach can cause costly production downtime or human risks.
This intermediate tutorial guides you step-by-step to fortify a typical Linux-based SCADA setup, covering network segmentation, protocol encryption (Modbus, OPC UA), multi-factor authentication, and proactive monitoring. We use open-source tools like iptables, OpenSSL, Fail2Ban, and Python, with complete, tested code. By the end, your setup will withstand advanced Nmap scans and injection attempts. Plan for 2-3 hours to implement on a test server. Bookmark this guide—it's built for production OT/IT engineers.
Prerequisites
- Linux server (Ubuntu 24.04 LTS or equivalent) with root access.
- Basic knowledge of networking (TCP/UDP ports), Linux, and Python 3.10+.
- Installed tools:
apt install iptables-persistent python3-pip fail2ban openssl. - Access to a simulated PLC (e.g., OpenPLC via Docker) or SCADA software like Ignition.
- Standard SCADA ports: 502 (Modbus TCP), 102 (S7Comm), 4840 (OPC UA).
Network Segmentation with iptables
#!/bin/bash
# Vider les règles existantes
iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X
iptables -t mangle -F
iptables -t mangle -X
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
# Interface réseau (adaptez eth0 à votre setup)
IFACE="eth0"
# Autoriser loopback
iptables -A INPUT -i lo -j ACCEPT
# Autoriser connexions établies
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Autoriser SSH (port 22) depuis IP admin (remplacez 192.168.1.0/24)
iptables -A INPUT -i $IFACE -p tcp --dport 22 -s 192.168.1.0/24 -j ACCEPT
# Ports SCADA : Modbus TCP (502), OPC UA (4840), S7 (102) - UNIQUEMENT depuis réseau OT sécurisé
iptables -A INPUT -i $IFACE -p tcp --dport 502 -s 10.0.1.0/24 -j ACCEPT
iptables -A INPUT -i $IFACE -p tcp --dport 4840 -s 10.0.1.0/24 -j ACCEPT
iptables -A INPUT -i $IFACE -p tcp --dport 102 -s 10.0.1.0/24 -j ACCEPT
# Interdire tout le reste
iptables -A INPUT -i $IFACE -j REJECT --reject-with icmp-host-prohibited
# Sauvegarder
netfilter-persistent save
# Log des rejets (optionnel)
iptables -A INPUT -j LOG --log-prefix "SCADA-DROP: " --log-level 4
echo "Firewall SCADA sécurisé activé. Testez avec: nmap -p 22,502,102,4840 votre_IP"This script sets up strict segmentation: only the OT network (10.0.1.0/24) can access SCADA ports, with SSH limited to admins. It uses conntrack for responses and logs suspicious attempts. Common pitfall: forgetting to save with netfilter-persistent, making rules vanish after reboot.
Why Segment the Network?
Segmentation (or zoning) is the cornerstone of SCADA defense, inspired by the Purdue model. Think of your network as a castle: the external DMZ blocks attackers, while the OT zone (PLC/SCADA) is isolated from IT. Here, iptables creates a default DROP zone, allowing only legitimate traffic. Test with nmap -sS your_IP: only authorized ports respond. Real-world benefit: an IT compromise won't reach the PLCs. Next up: encrypt those flows.
TLS Generation and Config for OPC UA
#!/bin/bash
# Créer CA auto-signé pour test (en prod, utilisez CA externe)
openssl genrsa -out ca.key 4096
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt -subj "/C=US/ST=Test/L=NewYork/O=Industrial/CN=SCADA-CA"
# Certificat serveur OPC UA
openssl genrsa -out server.key 4096
openssl req -new -key server.key -out server.csr -subj "/C=US/ST=Test/L=NewYork/O=Industrial/CN=opcua.scada.local"
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 365
# Certificat client (pour HMI/OPC client)
openssl genrsa -out client.key 4096
openssl req -new -key client.key -out client.csr -subj "/C=US/ST=Test/L=NewYork/O=Industrial/CN=hmi.scada.local"
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 365
# Concaténer pour OPC UA trust list (ex: pour open62541 ou Prosys OPC UA)
cat ca.crt server.crt > server-chain.pem
cat ca.crt client.crt > client-chain.pem
# Permissions sécurisées
chmod 600 *.key
chmod 644 *.crt *.pem
mkdir -p /etc/scada-tls
cp *.key *.crt *.pem /etc/scada-tls/
# Exemple config OPC UA server (adaptez à votre stack, ex: node-opcua-server)
echo '{"SecurityProfiles": ["Basic256Sha256_SignAndEncrypt"], "Certificate": "/etc/scada-tls/server-chain.pem"}' > opcua-config.json
echo "TLS OPC UA prêt. Importez ca.crt dans clients OPC UA pour trust."This script generates a full PKI for OPC UA (a modern SCADA protocol), enforcing SignAndEncrypt instead of vulnerable Basic128. In production, integrate Let's Encrypt or EJBCA. Pitfall: self-signed certs aren't trusted by default—always distribute the CA to clients. Test with opcua-client.
Encrypting Industrial Protocols
Analogy: Without TLS, your Modbus commands are like blank checks sent via regular mail—easy to intercept. OPC UA natively supports TLS 1.3; apply these certs in Ignition or Kepware.
For unencrypted Modbus TCP (port 502), tunnel via stunnel: stunnel modbus.conf with verifyChain = yes. Verify: openssl s_client -connect localhost:4840 -CAfile ca.crt. This blocks MITM attacks like the 2015 Ukraine incident.
Python Script for SCADA Log Monitoring
import re
import sys
import time
from pathlib import Path
from collections import defaultdict
LOG_FILE = '/var/log/scada-access.log'
ALERT_THRESHOLD = 5 # Tentatives par minute
SUSPICIOUS_IPS = set()
def tail_log():
with open(LOG_FILE, 'r') as f:
f.seek(0, 2)
while True:
line = f.readline()
if not line:
time.sleep(0.1)
continue
yield line
def parse_log(line):
# Regex pour extraire IP et port SCADA
match = re.match(r'SCADA-ACCESS: (\S+) \[(\d+)] "(?:GET|POST) /?(\d+)"', line)
if match:
ip, timestamp, port = match.groups()
return ip, int(port)
return None
def send_alert(ip):
print(f'🚨 ALERTE SCADA: IP {ip} suspecte ! Bloquez avec iptables -I INPUT -s {ip} -j DROP', file=sys.stderr)
# Intégrez Slack/Email: ex requests.post('webhook', json={'alert': ip})
if __name__ == '__main__':
rate_limit = defaultdict(int)
last_minute = time.time()
for line in tail_log():
event = parse_log(line)
if not event:
continue
ip, port = event
now = time.time()
if now - last_minute > 60:
rate_limit.clear()
last_minute = now
rate_limit[ip] += 1
if rate_limit[ip] > ALERT_THRESHOLD and port in [502, 102, 4840]:
send_alert(ip)
SUSPICIOUS_IPS.add(ip)
print('Monitoring SCADA actif. Ctrl+C pour arrêter.')This script monitors iptables logs in real-time to detect rapid scans on SCADA ports (Modbus brute-force). It alerts and suggests dynamic bans. Enhance with ELK Stack. Pitfall: forgetting pip install for added deps; test by generating traffic with nmap -p502,102,4840 localhost.
Proactive Monitoring and Anomaly Detection
Logs are your seismograph: this Python script parses SCADA-specific patterns (e.g., multiple Modbus Write commands). Real-world example: If an unknown IP hammers port 502, it auto-alerts. Integrate with systemd: systemctl enable scada-monitor. Analogy: like a guard counting visitors per hour.
Fail2Ban Config for SCADA Ports
[Definition]
failregex = ^SCADA-ACCESS: <HOST> \[.*\] "(?:GET|POST) /?(502|102)" \d+ \d+$
ignoreregex =Two Fail2Ban configs: one for Modbus/S7, one for OPC UA. It auto-bans IPs after 3 fails in 10 minutes. Complete with: fail2ban-client reload. Pitfall: poorly formatted logs—ensure iptables -j LOG. Check bans: fail2ban-client status scada-modbus.
Automating Bans with Fail2Ban
Fail2Ban turns logs into actions: IP bans + Mail/Slack notifications. For SCADA, set bantime to 24h+ since persistent attacks (APTs) are slow. Test: nc -zv your_IP 502 multiple times.
SSH Hardening for SCADA Admin Access
Port 2222
Protocol 2
PermitRootLogin no
PubkeyAuthentication yes
PasswordAuthentication no
ChallengeResponseAuthentication no
UsePAM no
X11Forwarding no
PrintMotd no
ClientAliveInterval 300
ClientAliveCountMax 0
LoginGraceTime 30
MaxAuthTries 3
AllowUsers scada-admin
Match User scada-admin
ForceCommand internal-sftp
ChrootDirectory /home/scada-admin
PermitTunnel noThis SSH config enforces public/private keys, chroots the SCADA user to SFTP-only, and uses a non-standard port. Restart: systemctl restart ssh. Pitfall: test connection first (ssh -p2222 user@ip) to avoid lockout. Perfect for IEC 62443-compliant audits.
Securing Access to SCADA Components
SSH hardening prevents rootkits via brute-force. Analogy: Keys instead of passwords, like a biometric safe. Add MFA with Google Authenticator for production.
Essential Best Practices
- Zero trust principle: Never assume implicit trust; validate every OT/IT flow.
- Monthly patches: Automate
unattended-upgradesfor kernel/firewall, test on staging. - Air-gapped backups: Rsync PLC/SCADA configs to a disconnected USB weekly.
- Regular audits:
lynis audit system+ Nessus scans on DMZ only. - Zoning documentation: Diagram your Purdue model with draw.io, version in Git.
Common Mistakes to Avoid
- Forgetting IPv6: Add identical
ip6tables; orsysctl net.ipv6.conf.all.disable_ipv6=1if unused. - Publicly exposed ports: Check
shodan.ioyour IP—no SCADA ports visible. - Expired certs: Cron script
openssl x509 -checkend 30 -noout server.crtfor alerts. - Unrotated logs:
logrotate /var/log/scada-*.logto prevent full disks during attacks.
Next Steps
Master IEC 62443 with our certifying Learni trainings. Resources: NIST SP 800-82r3 SCADA Guide, OpenPLC Security, Node-RED SCADA. Implement SIEM with ELK or Graylog for AI-powered alerts.