DEV Community

Cover image for SSH Port 22: Custom Ports, Port Forwarding Security, and Production SSH Hardening
Matt Keib, Tech Ed
Matt Keib, Tech Ed

Posted on

SSH Port 22: Custom Ports, Port Forwarding Security, and Production SSH Hardening

Most SSH hardening advice starts and ends with two changes: move off port 22 and disable root login. Both are easy to implement, both are widely recommended, and both are almost entirely misunderstood.

Port 22 is the TCP port that accepts the initial SSH handshake. It does not control how authentication is performed, which cryptographic algorithms are negotiated, or what a user is allowed to do after connecting. Changing the port does not strengthen encryption, alter the authentication model, or meaningfully restrict access.

Internet-facing SSH services on port 22 receive continuous automated traffic: credential stuffing attempts, key-based probes, and banner scans. Moving SSH to a non-standard port reduces this background noise and can make logs easier to analyze, but any exposed port will eventually be discovered and scanned.

The real security posture of an SSH deployment is determined by the cryptographic algorithms negotiated during key exchange, the authentication model enforced by sshd_config, forwarding rules that may create unintended access paths, and the presence or absence of connection rate limiting before sshd processes a request. Hardening SSH is about how access is defined, controlled, and enforced, not about where the service listens.

Port 22: What SSH actually controls

Port 22 controls exactly one thing: where the TCP handshake lands. It determines which packets sshd accepts for processing, and everything that follows is governed by sshd_config and the server's cryptographic configuration.

Tatu Ylönen requested port 22 from IANA in 1995, before he submitted the SSH protocol specification as an Internet Draft. He positioned SSH as a secure replacement for Telnet (23) and FTP (21), and chose port 22 because it was available and sat between them. The port number is a historical artifact of that decision, carrying no cryptographic or authentication significance.

Once a connection is established on port 22, the key exchange algorithm negotiated, the cipher selected, the authentication method chosen, and the session authorization enforced are all governed entirely by sshd_config directives and the server's cryptographic configuration. A server running on port 2222 with weak cipher suites and PasswordAuthentication yes has a materially larger attack surface than one on port 22 with algorithm restrictions and certificate-based authentication.

The argument for changing the default port is about noise reduction. An internet-facing server on port 22 absorbs continuous automated scanning, credential stuffing, and brute-force traffic from botnets that probe port 22 across the entire IPv4 address space. Moving to a non-standard port drops most of that traffic from your logs because those scanners do not prioritize non-standard ports at the same volume. That makes log analysis easier and removes some load from your PAM stack.

A targeted attacker who runs a port scan first will find SSH on any port. Every control in the sections that follow addresses a vulnerability class that port selection leaves intact. Changing the port can reduce log noise. It does not address the controls that define the broader SSH security posture.

Changing the default SSH port: The right way and the real caveats

Changing the SSH listening port is operationally straightforward, but execution matters far more than the change itself. Define an alternative port in sshd_config, reload the daemon, and SSH begins accepting connections on the new entry point.

The sequencing is critical. The new port must be allowed through the host firewall before the original port is closed. Reversing that order can immediately lock you out of the system, especially on remote infrastructure where no out-of-band access exists. This applies regardless of whether the system uses higher-level tools like UFW or direct packet filtering with nftables. Ensure the new path is open before removing the old one.

On systems running SELinux in enforcing mode, the port change introduces an additional constraint. SELinux maintains its own policy for which ports SSH is allowed to bind to. If the new port is not explicitly labeled for SSH, the daemon will fail to start on that port even if the configuration is otherwise correct. The service may stop accepting connections entirely if the change is made without updating the SELinux context.

Most operational issues with port changes arise in the surrounding ecosystem, not in the daemon itself. SSH client configurations that assume port 22 will continue to fail silently until updated. This includes ~/.ssh/config entries that omit an explicit port, as well as multi-hop configurations using ProxyJump, where the port must be updated across the entire chain and not just on the final target.

Monitoring and security tooling is another common blind spot. Many SIEM and availability checks assume SSH is reachable on port 22. Once the port changes, those systems may begin reporting hosts as offline or unreachable even though SSH is functioning normally on the new port. Updating monitoring rules before closing the original port avoids false alerts and unnecessary incident noise.

Changing the SSH port touches multiple layers: firewall policy, mandatory access controls, client configuration, and monitoring assumptions. Treat it as a coordinated change across your stack, not a single-line edit.

SSH port forwarding: Local, remote, dynamic, and their specific attack vectors

SSH port forwarding is widely used and often safe in practice, but each forwarding mode introduces a different class of risk. Treating all forwarding capabilities as equivalent in sshd_config ignores how they can be abused in real environments.

Forwarding is achieved by setting the following flags:

  • -L for local forwarding
  • -R for remote forwarding
  • -D for dynamic forwarding

Local forwarding (-L) binds a port on the client and tunnels traffic to a destination through the SSH connection. A common use case is accessing a remote database from a local workstation. By default, the forwarded port binds to the loopback interface, making it accessible only from the client itself. If explicitly bound beyond loopback, other devices that can reach that interface may also reach the forwarded service. In those cases, a service intended for local access is effectively exposed to the client's entire network environment. Local forwarding can also bypass network-level controls on the remote side by routing traffic through an already authenticated SSH session.

Remote forwarding (-R) exposes a port on the remote server that tunnels back to the client. This is useful for making a local service accessible through a remote host, but it also enables reverse tunnel abuse. A compromised or malicious client can establish an outbound SSH connection and open a reverse tunnel, effectively bypassing NAT and firewall restrictions because the connection originates from inside the network. If GatewayPorts is enabled, the forwarded port may be exposed beyond the remote host itself, increasing the blast radius. Keeping GatewayPorts disabled reduces unintended exposure in most environments.

Dynamic forwarding (-D) turns the SSH server into a SOCKS proxy, allowing the client to route arbitrary outbound traffic through the SSH connection. This is commonly used for secure browsing or testing from a different network perspective. The risk is reduced visibility: while the SSH session itself is logged, the application-level traffic passing through the SOCKS tunnel is not, making it a potential path for data exfiltration or policy bypass.

Each forwarding mode expands the role of SSH beyond interactive access into a general-purpose tunneling mechanism. Hardening SSH requires treating forwarding as a controlled capability, not a default convenience.

Forwarding is a feature, treat it like one

In many environments, forwarding is enabled by default but rarely required. If your SSH usage is limited to interactive access, leaving forwarding capabilities available expands the attack surface without providing operational value.

The safer baseline for those systems is to disable forwarding explicitly at the server level. OpenSSH provides a single directive for this purpose, with additional options that make the intent unambiguous to anyone reading the configuration.

DisableForwarding yes disables all forwarding types in one setting. This was introduced in OpenSSH 7.3 to simplify what previously required multiple directives. Versions 7.4 through 9.9 contained a logic error where certain forwarding modes were not consistently disabled in all cases. As a result, explicitly disabling X11, agent, and TCP forwarding alongside it remains a defensible practice.

This behavior was corrected in OpenSSH 9.9p2, but keeping the explicit directives is still useful. They provide clarity, document intent, and avoid relying on version-specific behavior when reviewing or maintaining configurations across mixed environments.

Hardening the cryptographic layer: KexAlgorithms, ciphers, and MACs

OpenSSH negotiates the strongest mutually supported algorithm between client and server. The problem with relying on this behavior alone is that "strongest mutually supported" is bounded by what both sides advertise. A legacy or misconfigured client can force the session down to weaker algorithms if the server allows them. The server defines the floor, and without explicitly restricting available algorithms, there is no meaningful floor.

KexAlgorithms

KexAlgorithms controls how the initial key exchange is performed.

Modern configurations prioritize algorithms like curve25519-sha256, along with high-strength Diffie-Hellman groups such as group16 and group18 with SHA-512. Older options like diffie-hellman-group1-sha1 and group14-sha1 should be removed due to known weaknesses, including susceptibility to attacks like Logjam. GSSAPI-based key exchange variants should also be excluded unless the environment explicitly depends on Kerberos.

Curve25519 is preferred in most deployments due to its performance characteristics and reduced risk of implementation flaws compared to older elliptic curve approaches.

Ciphers

Ciphers defines how session data is encrypted after the handshake.

Modern SSH deployments should restrict this list to authenticated encryption modes such as chacha20-poly1305@openssh.com and AES-GCM variants. Legacy ciphers like arcfour, 3des-cbc, and other CBC-mode constructions should be excluded. CBC-based modes have historically been associated with padding oracle attacks such as BEAST and Lucky13, while authenticated encryption modes combine confidentiality and integrity in a way that avoids the weaknesses associated with those attack classes.

MACs

MACs (Message Authentication Code) controls message authentication for algorithms that do not use authenticated encryption.

When MACs are required, encrypt-then-MAC (ETM) variants such as SHA-2-based and UMAC constructions should be preferred. Older options like MD5- or SHA1-based MACs should be removed. Non-ETM constructions have historically been more difficult to analyze securely and were part of the conditions that enabled timing-based attacks like Lucky13. ETM variants authenticate the ciphertext rather than the plaintext, improving resistance to those classes of attacks.

A hardened SSH configuration explicitly restricts key exchange, cipher, and MAC selections to modern, well-analyzed algorithms, rather than relying on default negotiation behavior.

A note on CVE-2024-6387 (regreSSHion): this vulnerability was a signal handler race condition in OpenSSH's authentication path, affecting versions 8.5p1 through 9.7p1 and enabling unauthenticated remote code execution. The algorithm restrictions above address a different class of risk. Cryptographic hardening mitigates weaknesses in negotiated algorithms, while patching OpenSSH addresses implementation flaws. Both are required.

Rate limiting and pre-auth defense: PerSourcePenalties, nftables, and PAM

Several sshd configuration parameters that are often left at their defaults have a direct impact on how the service behaves under brute-force and connection exhaustion attempts. Explicitly setting limits on authentication attempts, connection duration, and concurrent unauthenticated sessions establishes a baseline level of protection before introducing external controls.

Limiting authentication attempts per connection reduces the effectiveness of credential guessing within a single session. Shortening the grace period for unauthenticated connections bounds how long an attacker can hold a connection open without completing authentication. Controlling the number of concurrent unauthenticated sessions has the broadest impact: once a defined threshold is reached, sshd begins to probabilistically reject new connection attempts and eventually enforces a hard limit. This prevents connection exhaustion attacks from consuming all available capacity before legitimate users can authenticate.

Recent OpenSSH versions introduce PerSourcePenalties, a mechanism that applies automatic penalties to source addresses exhibiting abusive pre-authentication behavior. This is particularly relevant to vulnerabilities like CVE-2025-26466, where repeated pre-authentication interactions could consume excessive resources. PerSourcePenalties tracks and penalizes abusive sources before repeated pre-authentication activity consumes additional server resources. Reducing the impact of repeated malicious attempts. The directive requires OpenSSH 9.9p2 or later and should be configured explicitly rather than assumed to be active by default.

At the network layer, rate limiting can be enforced before sshd processes a connection at all. Packet filtering frameworks such as nftables allow per-source connection limits using connection tracking, ensuring that excessive connection attempts are dropped at the kernel level. This prevents sshd from allocating resources for abusive traffic and reduces log noise generated by repeated connection attempts. Systems that rely on older tooling can achieve similar behavior using iptables modules, though nftables provides a more modern and flexible approach.

Tools like Fail2Ban operate reactively by parsing logs and inserting firewall rules after repeated failures are detected. They introduce a dependency on user-space monitoring and response. Kernel-level rate limiting enforces decisions at packet ingress, eliminating the delay between detection and enforcement.

For environments that still rely on password-based authentication, PAM modules such as pam_faillock provide account-level protection by locking users after a defined number of failed attempts. This operates after a connection reaches sshd and complements network-level rate limiting. As organizations transition toward certificate-based authentication through tools like Teleport, PAM-based controls remain relevant for any remaining password-authenticated paths and should be treated as part of a layered defense strategy.

From authorized_keys to certificate authority: Fixing the credential model

The authorized_keys model works reasonably well at small scale. At larger scale, it becomes a distributed access management problem with no good solution inside the model itself.

Each host maintains its own file. Adding a new engineer requires propagating their key across multiple systems. Revoking access requires finding and removing that key everywhere it exists, with no guarantee that every copy was identified.

From an audit perspective, the model is equally weak. A successful login produces a key fingerprint, not a human identity tied to a specific authorization decision at a specific point in time. Access is granted based on the presence of a key in a file, not on a verified identity or a time-bound policy. At scale, this creates inconsistencies that compliance frameworks such as SOC 2 and PCI-DSS routinely flag.

SSH certificate authorities address this problem at the model level. Systems trust a central authority that signs short-lived certificates, so each host only needs to trust the CA once. Users authenticate to that authority, receive a certificate with a defined validity window, and use it to access systems. Revocation and expiration become properties of the certificate lifecycle rather than manual file edits on every host.

This shifts the problem from key distribution to CA lifecycle management. The certificate model eliminates static keys and simplifies revocation, but it introduces new operational requirements: managing the CA, issuing certificates at login time, and distributing revocation information consistently across systems. At smaller scales, this can be handled manually. At larger scales, the CA lifecycle itself becomes the bottleneck.

Teleport's Auth Service operates as a production-grade SSH certificate authority integrated with identity systems. Users authenticate through an existing identity provider with MFA, and Teleport issues short-lived certificates tied to that identity. Access is evaluated at session time using role-based policies, rather than relying on keys that were distributed at some point in the past.

Teleport integrates with existing OpenSSH nodes without requiring replacement of sshd. Hosts trust the Teleport CA, and user access is mediated through the Teleport control plane, which enforces authentication and authorization before routing connections to the target system. Teams can also record and review session activity through Teleport’s audit capabilities.

This model removes long-lived SSH keys as the primary access mechanism and replaces them with short-lived certificates issued to authenticated users under role-based policies. It also introduces a consistent audit layer, where access decisions and session activity are recorded in a consolidated system rather than inferred from distributed log entries.

For internal infrastructure, once access is fully mediated through Teleport, direct SSH exposure can be restricted or removed. Access is defined by who is allowed to connect and under what conditions, not by which port is open.

SSH Hardening solves configuration problems, not identity problems

The controls covered in this article address real weaknesses: weak cryptographic defaults, unbounded connection attempts, permissive forwarding, and the operational risks of exposing SSH directly to the network. Applied together, they significantly reduce the attack surface of a traditional SSH deployment, but they leave the underlying model intact.

Access is still granted based on static credentials. Authorization is still defined by what was configured at some point in the past. Revocation still depends on configuration changes propagating across systems. Even in a hardened state, SSH remains a system where access is managed indirectly through configuration rather than evaluated directly at connection time.

Systems trust short-lived certificates issued to authenticated identities. Access decisions are evaluated at session time, and expiration, revocation, and scope become properties of the credential itself.

Teleport operationalizes this model. Access is mediated through a control plane that handles authentication, authorization, and session recording before a connection reaches the target system. Certificates are issued on demand, tied to user identity and policy, and expire automatically. This reduces reliance on long-lived SSH keys and the operational work of distributing, rotating, and recovering static credentials after compromise.

At that point, the question shifts from how much more you can harden SSH to whether managing access through configuration is still the right model at all. Teleport replaces static SSH keys with short-lived certificates, enforces MFA at login, and provides consolidated access control and audit visibility.

Frequently Asked Questions

What does changing the SSH port from 22 actually accomplish?
Changing the SSH port from 22 to a non-standard port reduces automated scan noise from botnets that continuously probe port 22 across the IPv4 address space. It makes logs easier to analyze and removes some load from your PAM stack. It does not change your cryptographic configuration, alter the authentication model, or prevent a targeted attacker who runs a full port scan first.

Which SSH cipher algorithms should be disabled in sshd_config?
Remove legacy ciphers including arcfour, 3des-cbc, and all other CBC-mode constructions. CBC-based modes are associated with padding oracle attacks like BEAST and Lucky13. Modern SSH deployments should restrict the cipher list to authenticated encryption modes such as chacha20-poly1305@openssh.com and AES-GCM variants, which combine confidentiality and integrity in a single construction.

What is the risk of leaving SSH port forwarding enabled by default?
Leaving forwarding enabled expands the role of SSH beyond interactive access into a general-purpose tunnel. Local forwarding can expose remote services to unintended networks. Remote forwarding enables reverse tunnels that bypass NAT and firewall controls. Dynamic forwarding turns SSH into a SOCKS proxy, creating application-level data paths that are logged at the session level but not at the traffic level.

How does PerSourcePenalties differ from Fail2Ban for SSH protection?
PerSourcePenalties is built into OpenSSH (9.9p2 and later) and penalizes abusive source addresses at the daemon level before connection processing costs accumulate. Fail2Ban operates reactively by parsing logs and inserting firewall rules after repeated failures are detected. Kernel-level and daemon-level controls enforce decisions earlier in the connection lifecycle, while Fail2Ban introduces a dependency on user-space monitoring and response latency.

Why do SOC 2 auditors flag authorized_keys-based SSH access?
A successful login with an authorized_keys file produces a key fingerprint in logs, not a human identity tied to a specific authorization decision at a specific time. There is no built-in expiration, no atomic revocation across hosts, and no guarantee that a departing employee's key was removed from every system it was distributed to. SOC 2 and PCI-DSS frameworks flag this model because it grants access based on the presence of a static file rather than a verified, time-bound identity.

What is the difference between SSH certificate authority authentication and standard public key authentication?
With standard public key authentication, each host maintains its own authorized_keys file, and managing access at scale requires distributing and removing keys across every host individually. With certificate authority authentication, hosts trust a central CA that signs short-lived certificates. Users authenticate to the CA, receive a certificate with a defined validity window, and access expires naturally through that validity window rather than through manual file edits across every host.

How does DisableForwarding yes interact with OpenSSH versions before 9.9p2?
DisableForwarding yes was introduced in OpenSSH 7.3 to simplify forwarding controls into a single directive. Versions 7.4 through 9.9 contained a logic error where certain forwarding modes were not consistently disabled in all cases. The issue was corrected in 9.9p2. On older versions, explicitly disabling X11, agent, and TCP forwarding alongside DisableForwarding yes is the more reliable approach and also documents intent clearly for anyone reviewing the configuration.


If you have been through an SSH hardening exercise on a production fleet, share what surprised you most in the comments. Did the cryptographic defaults turn out to be weaker than expected? Did the forwarding rules create problems you did not anticipate?

Top comments (0)