{"id":169103,"date":"2026-06-18T19:26:18","date_gmt":"2026-06-18T16:26:18","guid":{"rendered":"https:\/\/computingforgeeks.com\/?p=169103"},"modified":"2026-06-18T19:26:18","modified_gmt":"2026-06-18T16:26:18","slug":"opensuse-leap-initial-server-setup","status":"publish","type":"post","link":"https:\/\/computingforgeeks.com\/opensuse-leap-initial-server-setup\/","title":{"rendered":"openSUSE Leap 16 Initial Server Setup and Hardening"},"content":{"rendered":"<p>A fresh openSUSE Leap 16 server is reachable over SSH the moment it boots, and straight out of the installer it still accepts password logins and lets root authenticate with a key. Before you put it on a public network, close the parts that are exposed by default.<\/p>\n\n<p>This guide hardens a new openSUSE Leap 16 server end to end: a non-root sudo account, key-only SSH, a locked-down firewall, SELinux left enforcing, fail2ban against brute force, and automatic updates now that YaST is gone. Every step was run on a real server, and the failure modes are called out where they bite.<\/p>\n\n<p><em>Tested in June 2026 on openSUSE Leap 16 with SELinux left enforcing throughout.<\/em><\/p>\n\n<h2>Prerequisites<\/h2>\n\n<p>You need a <a href=\"https:\/\/computingforgeeks.com\/install-opensuse-leap-16\/\">fresh openSUSE Leap 16 install<\/a> with root or initial sudo access, and a second terminal you keep open while you change anything to do with SSH. Do not skip the second terminal. The failure mode is locking yourself out of a remote box with no way back in.<\/p>\n\n<p>SELinux enforcing and an active firewalld are the Leap 16 defaults. This guide keeps both on. If you have only just finished the install, work through the general <a href=\"https:\/\/computingforgeeks.com\/things-to-do-after-installing-opensuse-leap\/\">post-install steps<\/a> first, then come back here to lock the server down.<\/p>\n\n<h2>Step 1: Create a non-root sudo user<\/h2>\n\n<p>Running day to day as root means every typo and every compromised process runs with full control of the box. Least privilege starts with a normal account that can escalate when it needs to. Create one and put it in the <code>wheel<\/code> group:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>useradd -m -G wheel -s \/bin\/bash ops\npasswd ops<\/code><\/pre>\n\n\n<p>On Leap 16 that group membership is all you need. The distribution ships a sudo policy that grants <code>wheel<\/code> members full sudo using their own password:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>sudo cat \/usr\/etc\/sudoers.d\/50-wheel-auth-self<\/code><\/pre>\n\n\n<p>The policy overrides the old openSUSE default that asked for the root password:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>Defaults:%wheel !targetpw\n%wheel ALL = (root) ALL<\/code><\/pre>\n\n\n<p>Note the path. Leap 16 moved vendor configuration to <code>\/usr\/etc<\/code> and reserves <code>\/etc<\/code> for your own overrides, so the shipped sudoers files live under <code>\/usr\/etc\/sudoers.d\/<\/code>. Leave them alone and add your changes in <code>\/etc<\/code>. Confirm the new account can escalate before you rely on it:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>su - ops -c \"sudo -v &amp;&amp; echo sudo works\"<\/code><\/pre>\n\n\n<p>It should prompt for the account&#8217;s own password and print <code>sudo works<\/code>. From here on, use this account and stop logging in as root.<\/p>\n\n<h2>Step 2: Set up SSH key authentication<\/h2>\n\n<p>Keys replace guessable passwords with a credential that brute force cannot grind down. Generate one on your own workstation, not on the server:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>ssh-keygen -t ed25519 -C \"ops@workstation\"<\/code><\/pre>\n\n\n<p>Copy the public key to the new account on the server:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>ssh-copy-id ops@SERVER_IP<\/code><\/pre>\n\n\n<p>Now test the key in a separate terminal before you change a single SSH setting. Do not skip this. If the key does not work and you have already disabled passwords in the next step, you are locked out:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>ssh ops@SERVER_IP<\/code><\/pre>\n\n\n<p>If that session opens without asking for a password, the key is in place and you can safely tighten the daemon.<\/p>\n\n<h2>Step 3: Harden the SSH daemon<\/h2>\n\n<p>openSUSE Leap 16 has no <code>\/etc\/ssh\/sshd_config<\/code> to edit. The shipped configuration lives at <code>\/usr\/etc\/ssh\/sshd_config<\/code> and pulls in drop-ins from <code>\/etc\/ssh\/sshd_config.d\/<\/code>. Add your hardening as a drop-in there so a package update never overwrites it. Open a new file:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>sudo vim \/etc\/ssh\/sshd_config.d\/99-hardening.conf<\/code><\/pre>\n\n\n<p>Put the lockdown in it:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>PermitRootLogin no\nPasswordAuthentication no\nKbdInteractiveAuthentication no\nPubkeyAuthentication yes\nX11Forwarding no\nMaxAuthTries 3\nAllowGroups wheel\nClientAliveInterval 300\nClientAliveCountMax 2<\/code><\/pre>\n\n\n<p><code>AllowGroups wheel<\/code> means only members of the <code>wheel<\/code> group can log in over SSH at all. Confirm your account is in that group, or you will lock yourself out the moment sshd reloads. Always validate the syntax and restart only if the check passes:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>sudo sshd -t &amp;&amp; sudo systemctl restart sshd<\/code><\/pre>\n\n\n<p>Gating the restart on <code>sshd -t<\/code> matters. A single typo in the drop-in stops sshd from coming back up, and on a remote box that is a lockout. Confirm the effective policy:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>sudo sshd -T | grep -E 'permitrootlogin|passwordauth|maxauthtries|allowgroups'<\/code><\/pre>\n\n\n<p>Root login and password authentication should both report off:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>permitrootlogin no\npasswordauthentication no\nmaxauthtries 3\nallowgroups wheel<\/code><\/pre>\n\n\n<p>The daemon now refuses root logins and passwords outright, accepting only keys from members of the <code>wheel<\/code> group.<\/p>\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1920\" height=\"556\" src=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-sshd-hardening-opensuse-leap.png\" alt=\"sshd -T showing PermitRootLogin no and PasswordAuthentication no on openSUSE Leap 16\" class=\"wp-image-169101\" title=\"\" srcset=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-sshd-hardening-opensuse-leap.png 1920w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-sshd-hardening-opensuse-leap-300x87.png 300w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-sshd-hardening-opensuse-leap-1024x297.png 1024w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-sshd-hardening-opensuse-leap-768x222.png 768w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-sshd-hardening-opensuse-leap-1536x445.png 1536w\" sizes=\"auto, (max-width: 1920px) 100vw, 1920px\" \/><\/figure>\n\n\n<p>With the daemon locked down, the next exposed surface is the firewall.<\/p>\n\n<h2>Step 4: Lock down the firewall<\/h2>\n\n<p>firewalld runs by default in the <code>public<\/code> zone. Check what it currently allows in:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>sudo firewall-cmd --list-services<\/code><\/pre>\n\n\n<p>A fresh install typically exposes more than SSH:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>cockpit dhcpv6-client ssh<\/code><\/pre>\n\n\n<p>The Cockpit web console is reachable on port 9090 unless you remove it. If this box is not a management host, take it out of the zone and reload:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>sudo firewall-cmd --permanent --remove-service=cockpit\nsudo firewall-cmd --reload<\/code><\/pre>\n\n\n<p>Expose only what the server actually serves. If you administer it from a known network, go further and accept SSH only from that subnet with a rich rule, which is stricter than leaving port 22 open to the world:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>sudo firewall-cmd --permanent --add-rich-rule='rule family=\"ipv4\" source address=\"192.168.1.0\/24\" service name=\"ssh\" accept'\nsudo firewall-cmd --permanent --remove-service=ssh\nsudo firewall-cmd --reload<\/code><\/pre>\n\n\n<p>One caution before you remove the broad <code>ssh<\/code> service: make sure your management subnet is correct, or you will cut off your own access. Test a new SSH session from inside that subnet before you log out.<\/p>\n\n<h2>Step 5: Keep SELinux enforcing<\/h2>\n\n<p>When a service misbehaves, the instinct is to run <code>setenforce 0<\/code> and move on. Do not. SELinux enforcing is a Leap 16 default and one of the few things separating a contained compromise from a total one. Confirm it is on:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>getenforce\nsudo sestatus<\/code><\/pre>\n\n\n<p>It should report enforcing with the targeted policy loaded:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>Enforcing\nSELinux status:                 enabled\nCurrent mode:                   enforcing\nLoaded policy name:             targeted<\/code><\/pre>\n\n\n<p>When something is blocked, find out what the policy actually denied instead of switching it off. The denials land in the audit log:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>sudo ausearch -m avc -ts recent<\/code><\/pre>\n\n\n<p>Read the denial, then fix it with the right boolean or file context (often a one-line <code>setsebool<\/code> or <code>semanage fcontext<\/code> plus <code>restorecon<\/code>). Disabling SELinux to make an error disappear trades a five-minute fix for a permanently weaker server.<\/p>\n\n<h2>Step 6: Block brute force with fail2ban<\/h2>\n\n<p><code>AllowGroups wheel<\/code> and key-only auth already shut the door on password guessing, but fail2ban cuts off the noise of repeated probes and the log spam that comes with it. Install it:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>sudo zypper install fail2ban<\/code><\/pre>\n\n\n<p>Configure a local jail so package updates never clobber your settings. Open a <code>jail.local<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>sudo vim \/etc\/fail2ban\/jail.local<\/code><\/pre>\n\n\n<p>Read the SSH journal and ban with firewalld so the bans use the same backend as the rest of your rules:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>[DEFAULT]\nbackend = systemd\nbantime = 1h\nfindtime = 10m\nmaxretry = 5\nbanaction = firewallcmd-rich-rules\n\n[sshd]\nenabled = true<\/code><\/pre>\n\n\n<p>Enable the service and confirm the jail is watching:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>sudo systemctl enable --now fail2ban\nsudo fail2ban-client status sshd<\/code><\/pre>\n\n\n<p>The jail reports the journal filter it matches and an empty ban list on a clean box:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>Status for the jail: sshd\n|- Filter\n|  `- Journal matches:\t_SYSTEMD_UNIT=sshd.service + _COMM=sshd\n`- Actions\n   `- Banned IP list:<\/code><\/pre>\n\n\n<p>Configuration options for tighter jails are covered in the <a href=\"https:\/\/www.fail2ban.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">fail2ban project documentation<\/a>, and the firewalld ban backend in the <a href=\"https:\/\/firewalld.org\/documentation\/\" target=\"_blank\" rel=\"noreferrer noopener\">firewalld documentation<\/a>.<\/p>\n\n<h2>Step 7: Enable automatic updates<\/h2>\n\n<p>An unpatched server is the most common way in, and with YaST retired there is no online-update module to lean on. The replacement on Leap 16 is the <code>os-update<\/code> package, which installs a systemd timer that patches the system on a schedule. Install it:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>sudo zypper install os-update<\/code><\/pre>\n\n\n<p>Enable the timer so updates run unattended:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>sudo systemctl enable --now os-update.timer<\/code><\/pre>\n\n\n<p>Confirm the next run is scheduled:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>systemctl list-timers os-update.timer<\/code><\/pre>\n\n\n<p>The timer fires daily and can signal rebootmgr when a kernel update needs a restart:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>NEXT                          LEFT  UNIT             ACTIVATES\nFri 2026-06-19 01:00:56 CEST  9h    os-update.timer  os-update.service<\/code><\/pre>\n\n\n<p>By default the timer applies every available update. To narrow it to security patches only, drop a one-line override that leaves the vendor config in <code>\/usr\/share\/os-update\/os-update.conf<\/code> untouched:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>echo \"UPDATE_CMD=security\" | sudo tee \/etc\/os-update.conf<\/code><\/pre>\n\n\n<p>For interactive patching between scheduled runs, the same updates apply through the standard <a href=\"https:\/\/computingforgeeks.com\/zypper-command-cheat-sheet\/\">zypper update commands<\/a>.<\/p>\n\n<h2>Step 8: Verify time synchronization<\/h2>\n\n<p>Accurate time is a security control, not a nicety. Log timestamps have to line up across hosts for an investigation to mean anything, and certificate and token validation both depend on the clock. Leap 16 installs chrony and runs it by default; confirm it is active and tracking a source:<\/p>\n\n\n<pre class=\"wp-block-code code\"><code>systemctl is-active chronyd\nchronyc -n sources<\/code><\/pre>\n\n\n<p>A healthy server shows reachable upstream sources with a low offset, which means the clock is disciplined and your logs are trustworthy.<\/p>\n\n<h2>Post-hardening security checklist<\/h2>\n\n<p>Before you call the server done, walk this list. Every item is one command, and every one should match the result shown. If any does not, that step is not finished.<\/p>\n\n<ol>\n<li><strong>Root SSH refused.<\/strong> <code>sudo sshd -T | grep permitrootlogin<\/code> returns <code>no<\/code>.<\/li>\n<li><strong>Passwords off.<\/strong> <code>sudo sshd -T | grep passwordauthentication<\/code> returns <code>no<\/code>.<\/li>\n<li><strong>Only admins can log in.<\/strong> <code>sudo sshd -T | grep allowgroups<\/code> returns <code>wheel<\/code>.<\/li>\n<li><strong>Firewall minimal.<\/strong> <code>sudo firewall-cmd --list-services<\/code> shows only what you serve, with no <code>cockpit<\/code>.<\/li>\n<li><strong>SELinux enforcing.<\/strong> <code>getenforce<\/code> returns <code>Enforcing<\/code>.<\/li>\n<li><strong>Brute force watched.<\/strong> <code>sudo fail2ban-client status sshd<\/code> shows the jail running.<\/li>\n<li><strong>Updates scheduled.<\/strong> <code>systemctl list-timers os-update.timer<\/code> shows a next run.<\/li>\n<li><strong>You are not root.<\/strong> You reached all of this as your <code>wheel<\/code> user through sudo, never by logging in as root.<\/li>\n<\/ol>\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1920\" height=\"1216\" src=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-server-hardening-audit-opensuse-leap.png\" alt=\"SELinux enforcing, firewalld services, fail2ban sshd jail and os-update timer on openSUSE Leap 16\" class=\"wp-image-169102\" title=\"\" srcset=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-server-hardening-audit-opensuse-leap.png 1920w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-server-hardening-audit-opensuse-leap-300x190.png 300w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-server-hardening-audit-opensuse-leap-1024x649.png 1024w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-server-hardening-audit-opensuse-leap-768x486.png 768w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-server-hardening-audit-opensuse-leap-1536x973.png 1536w\" sizes=\"auto, (max-width: 1920px) 100vw, 1920px\" \/><\/figure>\n\n\n<p>That covers the baseline every internet-facing Leap 16 server should have before it takes traffic. Layer service-specific controls on top as you add roles, and if you want a browser view of the running system to watch these controls hold, the <a href=\"https:\/\/computingforgeeks.com\/cockpit-web-console-opensuse-leap\/\">Cockpit console<\/a> reports SELinux, services, and accounts from one dashboard. SELinux being enforcing by default is one of the bigger shifts in this release, covered alongside the rest in <a href=\"https:\/\/computingforgeeks.com\/what-is-new-in-opensuse-leap-16\/\">what is new in Leap 16<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A fresh openSUSE Leap 16 server is reachable over SSH the moment it boots, and straight out of the installer it still accepts password logins and lets root authenticate with a key. Before you put it on a public network, close the parts that are exposed by default. This guide hardens a new openSUSE Leap &#8230; <a title=\"openSUSE Leap 16 Initial Server Setup and Hardening\" class=\"read-more\" href=\"https:\/\/computingforgeeks.com\/opensuse-leap-initial-server-setup\/\" aria-label=\"Read more about openSUSE Leap 16 Initial Server Setup and Hardening\">Read more<\/a><\/p>\n","protected":false},"author":13,"featured_media":169104,"comment_status":"open","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[299,47,50,75],"tags":[203,9986,205,167],"cfg_series":[39887],"class_list":["post-169103","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-how-to","category-linux","category-linux-tutorials","category-security","tag-firewall","tag-opensuse","tag-security","tag-ssh","cfg_series-opensuse-leap-16"],"_links":{"self":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/posts\/169103","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/users\/13"}],"replies":[{"embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/comments?post=169103"}],"version-history":[{"count":1,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/posts\/169103\/revisions"}],"predecessor-version":[{"id":169105,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/posts\/169103\/revisions\/169105"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/media\/169104"}],"wp:attachment":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/media?parent=169103"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/categories?post=169103"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/tags?post=169103"},{"taxonomy":"cfg_series","embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/cfg_series?post=169103"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}