{"id":61,"date":"2026-10-01T16:28:38","date_gmt":"2026-10-01T16:28:38","guid":{"rendered":"https:\/\/pomax-v3.weeltec.com\/?p=61"},"modified":"2026-10-01T16:28:38","modified_gmt":"2026-10-01T16:28:38","slug":"ssh-hardening-checklist-for-audits","status":"publish","type":"post","link":"https:\/\/pomax-v3.weeltec.com\/?p=61","title":{"rendered":"An SSH Hardening Checklist That Survives an Audit"},"content":{"rendered":"<div class=\"wt-post\">\n<style>.wt-post { --wt-bg: #0A0C0F; --wt-bg-2: #0E1116; --wt-surface: #12161C; --wt-line: #1F252E; --wt-line-strong: #2B333E; --wt-text: #E9ECEF; --wt-muted: #98A2AD; --wt-accent: #B9F24D; --wt-accent-hover: #C8FF5E; --wt-accent-dim: rgba(185, 242, 77, 0.10); --wt-accent-line: rgba(185, 242, 77, 0.25); --wt-danger: #F07A6B; --wt-ok: #7ED99A; --wt-sans: ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif; --wt-mono: ui-monospace, \"Cascadia Code\", \"JetBrains Mono\", \"SF Mono\", Menlo, Consolas, monospace; --wt-radius: 4px; --wt-h1: var(--wt-text); --wt-h2: var(--wt-text); --wt-h3: var(--wt-text); box-sizing: border-box; background: var(--wt-bg); color: var(--wt-text); font-family: var(--wt-sans); font-size: 1rem; line-height: 1.7; padding: clamp(1.75rem, 4vw, 3rem); border: 1px solid var(--wt-line); border-radius: 0; -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; } .wt-post *, .wt-post *::before, .wt-post *::after { box-sizing: border-box; } .wt-post ::selection { background: var(--wt-accent); color: var(--wt-bg); } .wt-post.wt-post p { color: var(--wt-text); font-family: var(--wt-sans); font-size: 1rem; line-height: 1.7; margin: 0 0 1.15rem; max-width: 68ch; } .wt-post.wt-post p:last-child { margin-bottom: 0; } .wt-post.wt-post h1, .wt-post.wt-post h2, .wt-post.wt-post h3, .wt-post.wt-post h4, .wt-post.wt-post h5, .wt-post.wt-post h6 { font-family: var(--wt-sans); font-weight: 700; letter-spacing: -0.025em; line-height: 1.15; text-wrap: balance; } .wt-post.wt-post h2 { color: var(--wt-h2); font-size: clamp(1.45rem, 3vw, 2rem); margin: 2.4rem 0 0.9rem; display: flex; align-items: baseline; gap: 0.6rem; } .wt-post.wt-post h2::before { content: \"\"; flex: none; width: 8px; height: 8px; background: var(--wt-accent); transform: translateY(-2px); } .wt-post.wt-post h3 { color: var(--wt-h3); font-size: 1.15rem; font-weight: 650; margin: 1.8rem 0 0.7rem; padding-left: 0.85rem; border-left: 2px solid var(--wt-accent-line); } .wt-post.wt-post h2:first-child, .wt-post.wt-post h3:first-child { margin-top: 0; } .wt-post.wt-post strong { color: #FFFFFF; font-weight: 650; } .wt-post.wt-post em { color: var(--wt-muted); font-style: italic; } .wt-post.wt-post a { color: var(--wt-accent); text-decoration: none; border-bottom: 1px solid var(--wt-accent-line); transition: color 0.15s ease, border-color 0.15s ease; } .wt-post.wt-post a:hover { color: var(--wt-accent-hover); border-bottom-color: var(--wt-accent-hover); } .wt-post.wt-post ul, .wt-post.wt-post ol { margin: 0 0 1.3rem; padding: 0; list-style: none; max-width: 68ch; } .wt-post.wt-post li { position: relative; padding-left: 1.6rem; margin-bottom: 0.55rem; color: var(--wt-text); line-height: 1.65; } .wt-post.wt-post ul > li::before { content: \"\\25AE\"; color: var(--wt-accent); position: absolute; left: 0; top: 0; font-size: 0.85em; line-height: 1.65; } .wt-post.wt-post ol { counter-reset: wt-li; } .wt-post.wt-post ol > li { counter-increment: wt-li; } .wt-post.wt-post ol > li::before { content: counter(wt-li) \".\"; font-family: var(--wt-mono); font-size: 0.8em; color: var(--wt-accent); position: absolute; left: 0; top: 0; line-height: 1.9; } .wt-post.wt-post blockquote { margin: 1.8rem 0; padding: 1.1rem 1.4rem; background: var(--wt-accent-dim); border-left: 2px solid var(--wt-accent); border-radius: 0; color: var(--wt-text); font-size: 1.05rem; font-style: normal; line-height: 1.6; } .wt-post.wt-post blockquote p { margin: 0; color: var(--wt-text); font-style: normal; } .wt-post.wt-post blockquote::before { content: none; } .wt-post.wt-post code, .wt-post.wt-post kbd, .wt-post.wt-post pre { font-family: var(--wt-mono); font-size: 0.88em; } .wt-post.wt-post code { background: var(--wt-surface); border: 1px solid var(--wt-line); border-radius: var(--wt-radius); padding: 0.1em 0.4em; color: var(--wt-accent); } .wt-post.wt-post pre { background: #0C0F13; border: 1px solid var(--wt-line-strong); border-radius: var(--wt-radius); padding: 1.1rem 1.25rem; overflow-x: auto; color: var(--wt-text); line-height: 1.7; margin: 0 0 1.3rem; } .wt-post.wt-post pre code { background: none; border: 0; padding: 0; color: inherit; } .wt-post.wt-post hr { border: 0; border-top: 1px solid var(--wt-line); margin: 2.2rem 0; } .wt-post.wt-post img { max-width: 100%; height: auto; border-radius: var(--wt-radius); border: 1px solid var(--wt-line); } @media (max-width: 640px) { .wt-post.wt-post h2 { font-size: 1.35rem; } .wt-post.wt-post blockquote { padding: 0.9rem 1.1rem; } } @media (prefers-reduced-motion: reduce) { .wt-post.wt-post a { transition: none; } }<\/style>\n<p>The SSH configuration that survives an audit is not the one with the longest file. It is the one where every directive is justified, documented, and applied from a single source instead of typed by hand once and forgotten.<\/p>\n<p>Nearly every SSH compromise follows the same short path: a key whose owner left the company, root login still permitted on one host nobody remembered, or password authentication enabled on a box that was \"temporarily\" exposed two years ago. A checklist is how you close that path and prove that you did.<\/p>\n<h2>What the auditor actually asks<\/h2>\n<p>Nobody reads sshd_config line by line. They ask a handful of questions and check whether your answers are demonstrable rather than aspirational.<\/p>\n<ul>\n<li>Who can log in to this host, and can you produce that list from a single source of truth?<\/li>\n<li>Is key rotation enforced, or is it a sentence in a policy nobody tracks?<\/li>\n<li>Can one stolen key reach the whole fleet?<\/li>\n<li>Are authentication failures logged centrally, and does anyone review them?<\/li>\n<li>Is the configuration managed from code, or does every host drift on its own?<\/li>\n<\/ul>\n<h2>The baseline configuration<\/h2>\n<h3>Authentication<\/h3>\n<p>Disable password and keyboard-interactive authentication outright. Set <strong>PermitRootLogin no<\/strong> and require named accounts with sudo. If root must be reachable in an emergency, use a console or a bastion, not an open door. Insist on modern key types \u2014 ed25519, or RSA at 4096 bits \u2014 and reject everything older.<\/p>\n<h3>Access scope<\/h3>\n<p>Put a bastion or jump host in front of production so no server accepts SSH from the public internet. Bind sshd to the management interface and use firewall rules as a second wall behind it. Use the <strong>AllowGroups<\/strong> directive to restrict which accounts each group can reach, so a single compromised user cannot pivot freely across the estate.<\/p>\n<h3>Session hygiene<\/h3>\n<p>Set short idle timeouts, disable port forwarding where it is not needed, cap concurrent unauthenticated connections, and turn off agent forwarding on shared hosts. These settings rarely break anything and remove entire classes of lateral movement.<\/p>\n<p>Where compliance demands it, enable session recording on privileged hosts and store the transcripts alongside the audit trail. It sounds heavy until the first incident review, when a transcript saves a week of guessing about who ran what and when.<\/p>\n<h2>Keys are inventory, not credentials<\/h2>\n<p>Every key needs an owner, a creation date, and a rotation date. Tag the key comment with the person and the host, store revocations wherever grants are recorded, and remove access the day someone leaves rather than at the next quarterly review. Better still, move to short-lived certificates signed by a small internal certificate authority, so access expires by default instead of accumulating quietly for years.<\/p>\n<p>Reachability is the other half of the problem. A key that still authenticates but is never used is a liability, so track last-used timestamps and retire anything dormant beyond a quarter.<\/p>\n<h2>Logging that proves the control works<\/h2>\n<p>Ship sshd logs to a central store the host itself cannot modify. Watch for failed authentication spikes, logins from unexpected regions, and successful logins by accounts that should never use SSH at all. A control nobody monitors is a control you cannot defend in a review.<\/p>\n<p>Put a named owner on that queue each week. Unassigned logs are read by nobody, and the reviewer is what turns raw lines into a defensible control.<\/p>\n<blockquote>\n<p>Hardening you cannot demonstrate is indistinguishable from hardening you never did.<\/p>\n<\/blockquote>\n<h2>How Weeltec applies this<\/h2>\n<p>Weeltec treats SSH access as configuration-managed state. Roles define who may reach which tier, keys and certificates are issued and revoked through automation, and each host is checked against the baseline on a fixed schedule, with drift raised as an incident rather than a footnote. By the time an audit arrives, the configuration history and revocation records already exist.<\/p>\n<h2>The rule<\/h2>\n<p>Turn off passwords. Scope access through a bastion. Inventory every key. Ship the logs somewhere the attacker cannot reach. Then re-run the check monthly and fix whatever drifted.<\/p>\n<p>None of this is exotic, and none of it is expensive. All of it has to be enforced by something other than good intentions \u2014 configuration management, a schedule, and a person who owns the result.<\/p>\n<p><em>Weeltec manages hardened, auditable server infrastructure for teams without a dedicated security function. If your SSH access model has never been reviewed, <a href=\"https:\/\/weeltec.com\/#contact\">get a quote<\/a> and we'll walk it with you.<\/em><\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>A practical SSH hardening checklist built around what auditors actually ask about: key management, access scope, enforced configuration, and logging that proves the controls work.<\/p>\n","protected":false},"author":1,"featured_media":58,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[35,36,29,34],"class_list":["post-61","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-server-management","tag-hardening","tag-security","tag-server-management","tag-ssh"],"_links":{"self":[{"href":"https:\/\/pomax-v3.weeltec.com\/index.php?rest_route=\/wp\/v2\/posts\/61","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/pomax-v3.weeltec.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/pomax-v3.weeltec.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/pomax-v3.weeltec.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/pomax-v3.weeltec.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=61"}],"version-history":[{"count":2,"href":"https:\/\/pomax-v3.weeltec.com\/index.php?rest_route=\/wp\/v2\/posts\/61\/revisions"}],"predecessor-version":[{"id":85,"href":"https:\/\/pomax-v3.weeltec.com\/index.php?rest_route=\/wp\/v2\/posts\/61\/revisions\/85"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/pomax-v3.weeltec.com\/index.php?rest_route=\/wp\/v2\/media\/58"}],"wp:attachment":[{"href":"https:\/\/pomax-v3.weeltec.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=61"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/pomax-v3.weeltec.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=61"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/pomax-v3.weeltec.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=61"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}