Skip to main content
Teksolvr
ブログに戻る
Infrastructure Hardening2026年7月4日9 min read

Hardening Linux Servers with SSH TLS and Firewall Rules

Alex Rivera, Senior Systems Architect

SSH Daemon Hardening

To harden the SSH daemon, edit /etc/ssh/sshd_config with the following directives. Each setting reduces the attack surface by disabling legacy protocols, weak ciphers, and unnecessary authentication methods.

ssh
# /etc/ssh/sshd_config
Port 22
Protocol 2
PermitRootLogin no
PubkeyAuthentication yes
PasswordAuthentication no
ChallengeResponseAuthentication no
UsePAM yes
X11Forwarding no
AllowTcpForwarding no
PermitEmptyPasswords no
MaxAuthTries 3
LoginGraceTime 30
ClientAliveInterval 300
ClientAliveCountMax 2
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512,hmac-sha2-256
KexAlgorithms curve25519-sha256@libssh.org,curve25519-sha256,diffie-hellman-group-exchange-sha256
HostKeyAlgorithms ssh-ed25519-cert-v01@openssh.com,ssh-ed25519,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-512

After editing, validate syntax and reload:

bash
sshd -t && systemctl reload sshd

Why these values?

  • Protocol 2 enforces SSH‑2 only (RFC 4253).
  • Ciphers and MACs restrict to AEAD algorithms providing confidentiality and integrity.
  • KexAlgorithms selects elliptic‑curve Diffie‑Hellman for forward secrecy.
  • HostKeyAlgorithms prefers Ed25519 and RSA‑SHA2‑512, eliminating SHA‑1 signatures.

Verification

bash
ssh -Q cipher | grep -E 'chacha20|aes.*gcm'
ssh -Q mac | grep -E 'hmac-sha2'
ssh -Q kex | grep -E 'curve25519|group-exchange-sha256'

TLS/SSL Configuration

Deploy TLS 1.2 and TLS 1.3 exclusively. The following Nginx snippet demonstrates a hardened server block. It references the Mozilla SSL Configuration Generator (intermediate profile) and aligns with NIST SP 800‑52 Rev. 2.

nginx
# /etc/nginx/conf.d/ssl-hardening.conf
server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers 'TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    ssl_session_tickets off;

    # OCSP Stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 1.1.1.1 8.8.8.8 valid=300s;
    resolver_timeout 5s;

    # Security Headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'nonce-$request_id'; style-src 'self' 'nonce-$request_id'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'" always;
    add_header X-Content-Type-Options nosniff always;
    add_header X-Frame-Options DENY always;
    add_header Referrer-Policy strict-origin-when-cross-origin always;
    add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
}

Reload Nginx:

bash
nginx -t && systemctl reload nginx

Cipher Suite Comparison

ProtocolCipher Suite (IANA)Forward SecrecyAEADRecommended
TLS 1.3TLS_AES_256_GCM_SHA384YesYes
TLS 1.3TLS_CHACHA20_POLY1305_SHA256YesYes
TLS 1.2ECDHE-ECDSA-AES256-GCM-SHA384YesYes
TLS 1.2ECDHE-RSA-AES256-GCM-SHA384YesYes
TLS 1.2ECDHE-ECDSA-CHACHA20-POLY1305YesYes
TLS 1.2ECDHE-RSA-CHACHA20-POLY1305YesYes
TLS 1.2AES256-GCM-SHA384 (static RSA)NoYes
TLS 1.1AnyN/AN/A

Source: IANA TLS Cipher Suite Registry

Firewall Rules

iptables Baseline

The following ruleset implements a default‑deny posture, permits established/related traffic, allows SSH (port 22) and HTTPS (port 443), and logs dropped packets for audit.

bash
# Flush existing rules
iptables -F
iptables -X
iptables -Z

# Default policies
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

# Loopback
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

# Established/related
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# ICMP (rate‑limited)
iptables -A INPUT -p icmp --icmp-type echo-request -m limit --limit 1/s --limit-burst 5 -j ACCEPT

# SSH (restrict to management subnet 10.0.0.0/24)
iptables -A INPUT -p tcp -s 10.0.0.0/24 --dport 22 -m conntrack --ctstate NEW -j ACCEPT

# HTTPS
iptables -A INPUT -p tcp --dport 443 -m conntrack --ctstate NEW -j ACCEPT

# Logging dropped packets
iptables -A INPUT -j LOG --log-prefix "IPT-DROP: " --log-level 4

# Save
iptables-save > /etc/iptables/rules.v4

Persist across reboots (Debian/Ubuntu):

bash
apt-get install -y iptables-persistent
netfilter-persistent save

UFW Equivalent

For administrators preferring UFW, the same policy translates to:

bash
ufw default deny incoming
ufw default allow outgoing
ufw allow from 10.0.0.0/24 to any port 22 proto tcp comment 'SSH mgmt'
ufw allow 443/tcp comment 'HTTPS'
ufw logging medium
ufw enable

Security Headers (CSP, HSTS, etc.)

The Nginx block above already injects a robust Content‑Security‑Policy (CSP) using nonces generated per request ($request_id). For Apache, the equivalent configuration:

apache
# /etc/apache2/conf-available/security-headers.conf
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'nonce-%{UNIQUE_ID}e'; style-src 'self' 'nonce-%{UNIQUE_ID}e'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'"
Header always set X-Content-Type-Options nosniff
Header always set X-Frame-Options DENY
Header always set Referrer-Policy strict-origin-when-cross-origin
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"

Enable with a2enconf security-headers && systemctl reload apache2.

Compliance Mapping

ControlFrameworkImplementation Evidence
AC‑3 (Access Enforcement)NIST SP 800‑53 Rev. 5SSH PermitRootLogin no, PasswordAuthentication no
SC‑8 (Transmission Confidentiality/Integrity)NIST SP 800‑53 Rev. 5TLS 1.2/1.3 only, AEAD ciphers
CM‑7 (Least Functionality)NIST SP 800‑53 Rev. 5Default‑deny firewall, minimal open ports
A.9.2.3 (Management of Privileged Access)ISO/IEC 27001:2022SSH key‑only auth, MaxAuthTries 3
A.13.1.1 (Network Controls)ISO/IEC 27001:2022iptables/UFW baseline ruleset
A.14.2.5 (Secure System Engineering)ISO/IEC 27001:2022CSP, HSTS, secure headers

References: NIST SP 800‑53 Rev. 5, ISO/IEC 27001:2022, OWASP Secure Headers Project.

Troubleshooting Checklist

  1. SSH Fails After Config Change

    a. Run sshd -t to validate syntax.
    b. Check journal: journalctl -u sshd -n 50 --no-pager.
    c. Verify client offers supported ciphers: ssh -vvv user@host 2>&1 | grep -i kex.
    d. Temporarily enable PasswordAuthentication yes to regain access, then re‑apply key‑only.

  2. TLS Handshake Errors
    a. Test with openssl s_client -connect example.com:443 -tls1_3.
    b. Confirm certificate chain: openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt /etc/letsencrypt/live/example.com/fullchain.pem.
    c. Ensure ssl_stapling resolver reaches OCSP responder (check logs for OCSP response not received).

  3. Firewall Blocks Legitimate Traffic
    a. List rules with line numbers: iptables -L INPUT -n --line-numbers.
    b. Verify source IP matches management subnet.
    c. Check dmesg or /var/log/kern.log for IPT-DROP entries.
    d. Insert a temporary allow rule for diagnostics: iptables -I INPUT 1 -s <test-ip> -j ACCEPT.

  4. CSP Violations in Browser Console
    a. Inspect Content-Security-Policy header via devtools.
    b. Ensure nonce generation matches script tags (<script nonce="{{request_id}}">).
    c. Adjust script-src directive to include required third‑party origins (e.g., https://cdn.example.com).

  5. Audit Log Review
    a. Centralize logs with rsyslog or systemd-journald forwarding.
    b. Correlate SSH auth failures (Failed password), firewall drops, and TLS alerts.
    c. Schedule weekly review against NIST SP 800‑92 log management guide.

Frequently Asked Questions (FAQ)

Q1: Why disable PasswordAuthentication entirely instead of using strong passwords?
A1: Password‑based auth is vulnerable to credential stuffing, brute‑force, and keylogging. Public‑key authentication provides cryptographic proof of possession without transmitting secrets. NIST SP 800‑63B recommends phasing out memorized secrets for privileged access.

Q2: Can I keep TLS 1.1 for legacy clients?
A2: No. TLS 1.1 lacks AEAD ciphers and is deprecated by RFC 8996. Maintaining it expands the attack surface (POODLE, BEAST). If legacy support is mandatory, isolate those clients on a segmented network with a dedicated termination proxy.

Q3: How do I rotate SSH host keys without downtime?
A3: Generate new keys (ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key_new), add them to sshd_config via HostKey /etc/ssh/ssh_host_ed25519_key_new, reload sshd, then after client verification remove the old key file and update config. This rolling approach avoids service interruption.

Q4: What is the impact of ssl_session_tickets off on performance?
A4: Disabling session tickets forces full handshakes for each new connection, increasing CPU usage (~5‑10 % on high‑traffic sites). However, tickets can weaken forward secrecy if the ticket key is compromised. For environments requiring PFS compliance (e.g., PCI‑DSS), keep tickets off and enable ssl_session_cache shared:SSL:10m for session resumption without<content>

このガイドのトラブルシューティングまたはテストをしていますか?

TeksolvrはDNS設定の検査、DKIM証明書の検証、ポート開放テスト、サーバーブラックリストの確認、計算のための97の無料ツールを提供しています。