Every SSH server announces itself before authentication begins. By default, that announcement includes the software version, and many organizations compound the problem with MOTD banners that display the hostname, IP address, kernel version, uptime, and system load. On an internet-facing bastion host, this is handing reconnaissance data to every scanner that connects.
What Gets Leaked
A typical unhardened bastion presents something like this upon connection:
SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13
Welcome to bastion01.example-corp.com (10.0.1.5)
Kernel: 6.5.0-44-generic
Uptime: 47 days, 3:22
Load: 0.12 0.08 0.03
Last login: Mon Mar 24 09:14:22 2026 from 10.0.1.100
This tells an attacker: the exact OpenSSH version (enabling targeted exploit selection), the operating system and kernel (narrowing the attack surface), the internal IP addressing scheme, system uptime (suggesting patch cadence), and that someone logged in from 10.0.1.100 recently (identifying a high-value pivot target).
The SSH protocol version string (SSH-2.0-OpenSSH_9.6p1) is part of the protocol handshake and cannot be fully suppressed without patching the binary. However, the banner, MOTD, and login messages are entirely within your control.
The Hardened Banner
Replace dynamic system information with a static legal warning banner. This serves dual purposes: it eliminates information leakage and satisfies compliance requirements for authorized-use warnings.
Create /etc/ssh/banner.txt:
*******************************************************************
AUTHORIZED ACCESS ONLY
This system is the property of [Organization Name].
Unauthorized access is prohibited and will be prosecuted
to the fullest extent of the law.
All activity is monitored and logged. By proceeding, you
consent to monitoring and agree to the acceptable use policy.
*****************************************************************
Configure /etc/ssh/sshd_config:
Banner /etc/ssh/banner.txt
PrintMotd no
PrintLastLog no
DebianBanner no
The DebianBanner no directive (available on Debian/Ubuntu) removes the OS identifier from the protocol version string, changing SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13 to SSH-2.0-OpenSSH_9.6p1. On RHEL-family systems, the OS string is not appended by default.
After these changes, a connection shows only:
*****************************************************************
AUTHORIZED ACCESS ONLY
...
*******************************************************************
No hostname. No IP. No kernel. No uptime.
Managing This with Puppet
In environments with multiple bastion hosts, manual configuration does not scale. A Puppet module handles this cleanly, with a parameter to distinguish bastion hosts from internal servers (which may legitimately benefit from informational MOTD content).
class ssh_banner (
Boolean $is_bastion = false,
) {
if $is_bastion {
file { '/etc/ssh/banner.txt':
ensure => file,
owner => 'root',
group => 'root',
mode => '0644',
source => 'puppet:///modules/ssh_banner/bastion_banner.txt',
}
file { '/etc/motd':
ensure => file,
content => '', # Empty MOTD
}
# Remove dynamic MOTD scripts
file { '/etc/update-motd.d':
ensure => directory,
recurse => true,
purge => true,
}
sshd_config { 'Banner':
value => '/etc/ssh/banner.txt',
}
sshd_config { 'PrintMotd':
value => 'no',
}
sshd_config { 'PrintLastLog':
value => 'no',
}
}
}
Apply with is_bastion => true on your edge-facing hosts and leave it false for internal servers where the system information MOTD may be useful for administrators.
Cleaning Up Dynamic Banner Scripts
Many Linux distributions ship with dynamic MOTD systems (Ubuntu’s update-motd.d, RHEL’s cockpit-motd) that regenerate banner content on login. These scripts populate /run/motd.dynamic or similar paths. Simply configuring PrintMotd no in sshd is not always sufficient — PAM modules may independently display MOTD content.
Check /etc/pam.d/sshd for lines like:
session optional pam_motd.so motd=/run/motd.dynamic
session optional pam_motd.so noupdate
Either comment these out or ensure the referenced files are empty. In the Puppet module, manage the PAM configuration as well:
file_line { 'disable_pam_motd_dynamic':
path => '/etc/pam.d/sshd',
match => 'pam_motd.so.motd=/run/motd.dynamic',
line => '# session optional pam_motd.so motd=/run/motd.dynamic',
}
If other Puppet modules manage PAM configuration, be cautious of conflicts. Use require or before ordering to ensure your SSH banner module applies after the base PAM configuration.
Cron Cleanup for Dynamic MOTD
Some systems regenerate dynamic MOTD content via cron (for instance, a landscape-sysinfo job on Ubuntu, or a custom script displaying disk usage). Even after purging /etc/update-motd.d, these cron entries may recreate content. Add a cron resource to your Puppet module that ensures cleanup:
cron { 'purge_dynamic_motd':
command => '/bin/echo -n > /run/motd.dynamic 2>/dev/null; true',
minute => '/30',
user => 'root',
}
This is a belt-and-suspenders measure. It should not be necessary if the PAM and update-motd configurations are correct, but it provides a safety net against rogue packages that reinstall MOTD scripts during updates.
Compliance Context
Legal warning banners are not optional vanity text — they are compliance requirements across multiple frameworks:
- NIST 800-53 AC-8: Requires display of a system use notification before granting access, including conditions of use, monitoring notice, and legal consequences of unauthorized access
- FISMA: Mandates AC-8 implementation for all federal information systems
- DISA STIG: V-230225 (RHEL 8) and equivalent controls require DoD-standard warning banners on all interactive login points
- CIS Benchmarks: Section 5.2.16 (and similar) requires a warning banner and disabling of system information in the login process
The exact banner text varies by jurisdiction and organization. DoD environments require a specific verbatim notice. Commercial organizations should have legal counsel draft the text. The technical implementation is the same regardless of the content.
Verification
After deploying the hardened configuration, verify from an external host:
# Check the pre-auth banner
ssh -o PreferredAuthentications=none -o ConnectTimeout=5 bastion01.example-corp.com 2>&1
# Check the protocol version string
echo "" | nc -w3 bastion01.example-corp.com 22
The first command should display only your legal banner and an authentication prompt. The second should return a version string with no OS identifier. If you see hostnames, IPs, kernel versions, or uptime anywhere in the output, something was missed.
SSH banner hardening is a small change with disproportionate security value. It costs nothing, takes minutes to implement, satisfies multiple compliance controls, and removes low-hanging reconnaissance fruit from every automated scanner and opportunistic attacker that touches your perimeter.
