Description
These OpenSSH server options limit resources used per client and detect dead connections:
ClientAliveInterval(1 to {{ ssh_client_alive_interval }} seconds) andClientAliveCountMax(1 to {{ ssh_client_alive_count_max }}): the server checks that the client still answers, and closes the session afterClientAliveCountMaxunanswered checks;MaxStartupsat most10:30:60: beyond 10 unauthenticated connections, new ones are refused with a probability of 30 %, rising to 100 % at 60;MaxSessionsat most {{ ssh_max_sessions }}: shells, commands and subsystems multiplexed on one connection.
OpenSSH's own default for ClientAliveInterval is 0 (no check); the Debian cloud images set
120. Idle shell sessions are closed by TMOUT (CB-DEB-AUTH-020), not by these options, since
a live client always answers keepalive checks. The fix only changes the options that are out of
bounds, in /etc/ssh/sshd_config.d/00-cirsec.conf.
Rationale
Sessions whose client has gone away (laptop closed, network cut) keep a shell open on the server; closing them limits what a later session hijack can reach. Connection limits keep a brute force tool or a flood of half-open connections from making the server unreachable for administrators.
Impact
Sessions of clients that stop answering for more than {{ ssh_client_alive_interval }} ×
{{ ssh_client_alive_count_max }} seconds are closed. Automation that opens many parallel
connections or multiplexed sessions (Ansible pipelining, ControlMaster) may hit the limits;
raise them through tailoring if needed.
Applies when
Detected: when: OpenSSH server is installed A scanner reports the control not applicable when one is false.
Setting
| Location | /etc/ssh/sshd_config.d/00-cirsec.conf |
| Default | ClientAliveInterval 0 (120 on cloud images), ClientAliveCountMax 3, MaxStartups 10:30:100, MaxSessions 10 |
| Recommended | ClientAliveInterval 1-{{ ssh_client_alive_interval }}, ClientAliveCountMax 1-{{ ssh_client_alive_count_max }}, MaxStartups <= 10:30:60, MaxSessions <= {{ ssh_max_sessions }} |
Parameters
| Parameter | Core | Server | Server hardened | Desktop | Desktop hardened | Bounds |
|---|---|---|---|---|---|---|
ssh_client_alive_intervalinteger, seconds. Maximum interval between two keepalive checks of the client. | 300 | 300 | 300 | 300 | 300 | 1 to 900 |
ssh_client_alive_count_maxinteger. Maximum number of unanswered keepalive checks before the session is closed. | 3 | 3 | 3 | 3 | 3 | 1 to 10 |
ssh_max_sessionsinteger. Maximum sessions per SSH connection. | 10 | 10 | 10 | 10 | 10 | 1 to 10 |
The value used is your override (tailoring file), else the profile's, else the one of the profile it extends, else the default. Bounds limit what an override may set.
Audit
Run sshd -T | grep -E '^(clientaliveinterval|clientalivecountmax|maxstartups|maxsessions) '.
clientaliveinterval must be between 1 and {{ ssh_client_alive_interval }},
clientalivecountmax between 1 and {{ ssh_client_alive_count_max }}, each field of
maxstartups at most 10:30:60, and maxsessions at most {{ ssh_max_sessions }}.
Automated: every check must pass. Scanners report each check by its id.
client-alive-interval-set
ClientAliveInterval is not 0 (keepalive off).
key: clientaliveinterval
operator: '>='
value: 1
client-alive-interval
ClientAliveInterval upper bound.
key: clientaliveinterval
operator: <=
value: '{{ ssh_client_alive_interval }}'
client-alive-count-set
ClientAliveCountMax is not 0.
key: clientalivecountmax
operator: '>='
value: 1
client-alive-count
ClientAliveCountMax upper bound.
key: clientalivecountmax
operator: <=
value: '{{ ssh_client_alive_count_max }}'
max-startups
MaxStartups at most 10:30:60.
key: maxstartups
operator: match
value: ^([1-9]|10):([1-9]|[12][0-9]|30):([1-9]|[1-5][0-9]|60)$
max-sessions
MaxSessions upper bound.
key: maxsessions
operator: <=
value: '{{ ssh_max_sessions }}'
Remediation
Add to /etc/ssh/sshd_config.d/00-cirsec.conf:
ClientAliveInterval {{ ssh_client_alive_interval }}
ClientAliveCountMax {{ ssh_client_alive_count_max }}
MaxStartups 10:30:60
MaxSessions {{ ssh_max_sessions }}
Check with sshd -t, then run systemctl reload ssh.
| Restart | service: ssh |
| Disruption | low |
| Lockout risk | yes: can cut off administrative access. Off by default in generated scripts and playbooks, opt in through a tailoring file. |
Rollback
Remove the lines from /etc/ssh/sshd_config.d/00-cirsec.conf and run systemctl reload ssh.
limits
# CB-DEB-NET-017: bring SSH keepalive and connection limits within bounds, changing only what is out of bounds.
PATH="$PATH:/usr/sbin"
f=/etc/ssh/sshd_config.d/00-cirsec.conf
grep -Eqi '^[[:space:]]*Include[[:space:]]+/etc/ssh/sshd_config.d/\*\.conf' /etc/ssh/sshd_config || {
echo '/etc/ssh/sshd_config does not include sshd_config.d/*.conf' >&2
exit 1
}
cfg=$(sshd -T 2>/dev/null) || { echo 'sshd -T failed' >&2; exit 1; }
get() { printf '%s\n' "$cfg" | awk -v k="$1" '$1 == k { print $2; exit }'; }
# in_range VALUE MAX: VALUE is an integer between 1 and MAX.
in_range() { case $1 in '' | *[!0-9]*) return 1 ;; esac; [ "$1" -ge 1 ] && [ "$1" -le "$2" ]; }
changes=
in_range "$(get clientaliveinterval)" "$CB_SSH_CLIENT_ALIVE_INTERVAL" || changes="$changes clientaliveinterval=$CB_SSH_CLIENT_ALIVE_INTERVAL"
in_range "$(get clientalivecountmax)" "$CB_SSH_CLIENT_ALIVE_COUNT_MAX" || changes="$changes clientalivecountmax=$CB_SSH_CLIENT_ALIVE_COUNT_MAX"
in_range "$(get maxsessions)" "$CB_SSH_MAX_SESSIONS" || changes="$changes maxsessions=$CB_SSH_MAX_SESSIONS"
get maxstartups | grep -Eq '^([1-9]|10):([1-9]|[12][0-9]|30):([1-9]|[1-5][0-9]|60)$' || changes="$changes maxstartups=10:30:60"
[ -n "$changes" ] || exit 0
mkdir -p /etc/ssh/sshd_config.d
cb_backup "$f"
touch "$f"
chmod 600 "$f"
for c in $changes; do
k=${c%%=*}
sed -i "/^[[:space:]]*${k}[[:space:]]/Id" "$f"
printf '%s %s\n' "$k" "${c#*=}" >>"$f"
done
if ! sshd -t; then
cb_restore "$f"
echo 'sshd -t rejected the change, reverted' >&2
exit 1
fi
Handwritten code reads parameters from CB_<NAME> environment variables. Each release turns these fixes into remediate.sh and an Ansible role per profile: dry run by default, a fix runs only when its check fails.
References
- sshd_config(5), Debian manual pages Debian, vendor-doc, accessed 2026-10-18
- NIST IR 7966 - Security of Interactive and Automated Access Management Using Secure Shell (SSH) NIST, guide, accessed 2026-10-18
Mappings
| Framework | ID | Relation |
|---|---|---|
| NIST SP 800-53 Rev. 55.1.1 | SC-10 | partial |
| NIST SP 800-53 Rev. 55.1.1 | SC-5 | related |
How this control relates to the other framework's: equivalent, stricter, looser, partial or related.
Threats mitigated
Related controls
- related
CB-DEB-AUTH-020Close idle shell sessions automatically
People and lifecycle
| Authors |
|
| Verified | 2026-10-18 by Sn0wAlice on 12, Proxmox VM 921, Debian 12 genericcloud image, kernel 6.1.0-54-cloud-amd64. fix.sh sets MaxStartups 10:30:60 on stock config, and interval/count when set to 0; SSH by key still accepted. |
| Verified | 2026-10-18 by Sn0wAlice on 13, Proxmox VM 931, Debian 13 genericcloud image, kernel 6.12.113+deb13-cloud-amd64. fix.sh sets MaxStartups 10:30:60 on stock config, and interval/count when set to 0; SSH by key still accepted. |
| Introduced in | v0.1.0 |
| Created, updated | 2026-10-18, 2026-10-18 |
| Translations | fr |