<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Ssh – Serverküche</title><link>https://serverkueche.de/en/tags/ssh/</link><description>Ssh – Neueste Beiträge von Serverküche</description><generator>Hugo</generator><language>en-US</language><managingEditor>feedback@serverkueche.de (Serverküche)</managingEditor><webMaster>feedback@serverkueche.de (Serverküche)</webMaster><copyright>2026 Serverküche</copyright><lastBuildDate>Sat, 18 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://serverkueche.de/en/tags/ssh/index.xml" rel="self" type="application/rss+xml"/><item><title>Setting up Fail2ban: block brute-force attacks automatically</title><link>https://serverkueche.de/en/tutorials/fail2ban-setup/</link><pubDate>Sat, 18 Jul 2026 00:00:00 +0000</pubDate><author>feedback@serverkueche.de (Serverküche)</author><guid>https://serverkueche.de/en/tutorials/fail2ban-setup/</guid><description>Fail2ban watches your logs and bans IPs after too many failed attempts – with a safe whitelist for your own address so you don't lock yourself out.</description><content:encoded><![CDATA[<p>After a few days on the internet, take a look at <code>journalctl -u ssh</code>: hundreds of
login attempts from foreign IPs, one every second. As long as your SSH is
<a href="/en/tutorials/harden-ssh/">switched to key login</a>, none of them gets in – but it
clutters the log and eats resources. <strong>Fail2ban</strong> reads along with these logs and
<strong>bans automatically</strong> whoever tries too often without success.</p>
<h2 id="what-are-we-building">What are we building?</h2>
<p>By the end, <strong>Fail2ban 1.1</strong> on your <strong>Debian 13</strong> monitors the SSH logins and, after
too many failed attempts, bans the attacking IP for a defined time – via a firewall
rule. <strong>Your own IP is on a whitelist</strong> so this never hits you. You can view bans, set
them manually and lift them again.</p>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li>A <a href="/en/tutorials/harden-ssh/">hardened server</a> (key login) with an
<a href="/en/tutorials/firewall-ufw-setup/">active firewall</a></li>
<li>Your <strong>own public IP</strong> – find it e.g. via
<a href="https://www.wieistmeineip.de">wieistmeineip.de</a> or <code>curl -s ifconfig.me</code> from your
local machine</li>
</ul>
<h2 id="step-by-step">Step by step</h2>
<p>Before we configure, three terms that explain the whole tool:</p>
<ul>
<li><strong>Filter</strong> – a pattern that detects a &ldquo;failed login&rdquo; in the log (for SSH, Fail2ban
brings this ready-made).</li>
<li><strong>Jail</strong> – connects a filter with a log source and the rules &ldquo;how often in what time
frame&rdquo;. The <code>sshd</code> jail monitors the SSH logins.</li>
<li><strong>Action</strong> – what happens when it&rsquo;s exceeded: by default a <strong>firewall rule</strong> that
blocks the IP for <code>bantime</code>.</li>
</ul>
<p>In short: filter detects failed attempts → jail counts them → action bans. Everything
you configure below is one of these three knobs.</p>
<h3 id="step-1-install-fail2ban">Step 1: Install Fail2ban</h3>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo apt update
</span></span><span class="line"><span class="cl">sudo apt install -y fail2ban</span></span></code></pre></div>
</div>
<p>Check the version:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">fail2ban-client --version</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Fail2Ban v1.1.0</span></span></code></pre></div>
</div>
<h3 id="step-2-your-own-configuration-in-jaillocal">Step 2: Your own configuration in jail.local</h3>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-amber-400 bg-amber-50 dark:border-amber-700 dark:bg-amber-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">⚠️</span>Never edit jail.conf
  </p>
  <div class="prose-kitchen text-sm">The shipped <code>/etc/fail2ban/jail.conf</code> is overwritten on updates. Your own settings
<strong>always</strong> belong in <code>/etc/fail2ban/jail.local</code> – this file is preserved and overrides
the defaults.</div>
</div>
<p>Create the file:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo nano /etc/fail2ban/jail.local</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">INI</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[DEFAULT]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># How long the ban lasts</span>
</span></span><span class="line"><span class="cl"><span class="na">bantime</span> <span class="o">=</span> <span class="s">1h</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Time window in which the failed attempts count</span>
</span></span><span class="line"><span class="cl"><span class="na">findtime</span> <span class="o">=</span> <span class="s">10m</span>
</span></span><span class="line"><span class="cl"><span class="c1"># This many failed attempts are allowed, then a ban</span>
</span></span><span class="line"><span class="cl"><span class="na">maxretry</span> <span class="o">=</span> <span class="s">5</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Your own IP(s) – will NEVER be banned</span>
</span></span><span class="line"><span class="cl"><span class="na">ignoreip</span> <span class="o">=</span> <span class="s">127.0.0.1/8 ::1 YOUR_OWN_IP</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[sshd]</span>
</span></span><span class="line"><span class="cl"><span class="na">enabled</span> <span class="o">=</span> <span class="s">true</span></span></span></code></pre></div>
</div>
<p>Line by line:</p>
<ul>
<li><strong><code>bantime = 1h</code></strong> – ban duration. For stubborn cases, see the escalation in step 5.</li>
<li><strong><code>findtime</code> + <code>maxretry</code></strong> – &ldquo;5 failed attempts within 10 minutes → ban&rdquo;.</li>
<li><strong><code>ignoreip</code></strong> – the most important line: enter <strong>your own IP</strong> here so you don&rsquo;t lock
yourself out. If you have a changing IP at home, better use access via a fixed point
(a VPN later) instead of a broad allowance.</li>
<li><strong><code>[sshd] enabled = true</code></strong> – activates the SSH jail.</li>
</ul>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-amber-400 bg-amber-50 dark:border-amber-700 dark:bg-amber-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">⚠️</span>Enter your own IP first
  </p>
  <div class="prose-kitchen text-sm">Set your own IP in <code>ignoreip</code> <strong>before</strong> you start Fail2ban – otherwise even your own
typo on login could lock you out. The address you&rsquo;re currently connected from is shown
by the first field of <code>echo &quot;$SSH_CLIENT&quot;</code>.</div>
</div>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-herb-400 bg-herb-50 dark:border-herb-700 dark:bg-herb-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">🧑‍🍳</span>Debian 13 reads the systemd journal
  </p>
  <div class="prose-kitchen text-sm">On Debian 13, Fail2ban uses the <strong>systemd journal</strong> as its source by default – you
don&rsquo;t need to specify a <code>logpath</code> (like <code>/var/log/auth.log</code>). On modern systems that
file often no longer exists at all; the journal is the right source and works without
extra configuration.</div>
</div>
<h3 id="step-3-start-and-enable-the-service">Step 3: Start and enable the service</h3>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo systemctl <span class="nb">enable</span> --now fail2ban</span></span></code></pre></div>
</div>
<p><code>enable --now</code> starts the service immediately <strong>and</strong> ensures it runs again
automatically after a reboot. Check that it runs cleanly:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo systemctl status fail2ban</span></span></code></pre></div>
</div>
<p>You should see <code>active (running)</code>. After every change to <code>jail.local</code>, reload the
configuration with <code>sudo systemctl reload fail2ban</code>.</p>
<h3 id="step-4-check-the-status">Step 4: Check the status</h3>
<p>This is how you see which jails are active:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo fail2ban-client status</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Status
</span></span><span class="line"><span class="cl">|- Number of jail:	1
</span></span><span class="line"><span class="cl">`- Jail list:	sshd</span></span></code></pre></div>
</div>
<p>And the details of the SSH jail:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo fail2ban-client status sshd</span></span></code></pre></div>
</div>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">Status for the jail: sshd
</span></span><span class="line"><span class="cl">|- Filter
</span></span><span class="line"><span class="cl">|  |- Currently failed:	0
</span></span><span class="line"><span class="cl">|  |- Total failed:	0
</span></span><span class="line"><span class="cl">|  `- Journal matches:	_SYSTEMD_UNIT=ssh.service + _COMM=sshd
</span></span><span class="line"><span class="cl">`- Actions
</span></span><span class="line"><span class="cl">   |- Currently banned:	0
</span></span><span class="line"><span class="cl">   |- Total banned:	0
</span></span><span class="line"><span class="cl">   `- Banned IP list:</span></span></code></pre></div>
</div>
<p>The <code>Journal matches</code> line confirms that Fail2ban reads the systemd journal.
<code>Currently banned</code> rises as soon as someone gets it wrong too often.</p>
<h3 id="step-5-manage-bans--and-escalate-them">Step 5: Manage bans – and escalate them</h3>
<p>Manually unban an IP (e.g. when a colleague mistyped):</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo fail2ban-client <span class="nb">set</span> sshd unbanip 203.0.113.45</span></span></code></pre></div>
</div>
<p>Ban an IP immediately:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo fail2ban-client <span class="nb">set</span> sshd banip 203.0.113.45</span></span></code></pre></div>
</div>
<p>Against especially stubborn attackers, an <strong>escalating ban time</strong> pays off: whoever
comes back is banned for longer. Add to the <code>[DEFAULT]</code> block:</p>
<div class="sk-code">
  <span class="sk-code-head">INI</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">bantime.increment</span> <span class="o">=</span> <span class="s">true</span>
</span></span><span class="line"><span class="cl"><span class="na">bantime.maxtime</span> <span class="o">=</span> <span class="s">1w</span></span></span></code></pre></div>
</div>
<p>With that, Fail2ban doubles the ban time each time the same IP reoffends – up to a
maximum of one week. After the change, <code>sudo systemctl reload fail2ban</code>.</p>
<h2 id="when-things-go-wrong">When things go wrong</h2>
<div class="troubleshoot not-prose">
<p><strong>You locked yourself out.</strong> <code>ignoreip</code> was missing or contained the wrong IP. Connect
via the <strong>console in the netcup SCP</strong> (independent of SSH) and unban yourself with
<code>sudo fail2ban-client set sshd unbanip YOUR_OWN_IP</code>. Then enter your IP in <code>ignoreip</code>
and reload. That&rsquo;s why the whitelist comes first in step 2.</p>
<p><strong><code>fail2ban.service</code> won&rsquo;t start (<code>systemctl status</code> shows &ldquo;failed&rdquo;).</strong> Almost always
a typo in <code>jail.local</code>. Check the syntax with <code>sudo fail2ban-client -t</code> (test mode) –
the command names the faulty line.</p>
<p><strong>No one is ever banned, even though the log is full of failed attempts.</strong> Check with
<code>sudo fail2ban-client status sshd</code> whether <code>Total failed</code> rises at all. If it stays at
0, the filter isn&rsquo;t finding the entries – usually because an outdated guide set a
<code>logpath</code> to a non-existent file. On Debian 13, <strong>remove</strong> the <code>logpath</code> from
<code>jail.local</code> and use the journal (step 2).</p>
<p><strong>Bans &ldquo;don&rsquo;t work&rdquo; – the IP keeps connecting.</strong> Fail2ban writes its block rules into
the same firewall (nftables/iptables) as UFW. Check with <code>sudo nft list ruleset | grep f2b</code> (or <code>sudo iptables -L -n | grep f2b</code>) whether the <code>f2b</code> chains exist. If they&rsquo;re
missing, the interplay with the firewall is stuck – restart the service and look at
the logs.</p>

</div>

<h2 id="maintenance--backups">Maintenance &amp; backups</h2>
<ul>
<li><strong>Keep an eye on bans:</strong> <code>sudo fail2ban-client status sshd</code> shows at any time how
many IPs are currently banned. A look at the journal (<code>journalctl -u fail2ban</code>) shows
the history.</li>
<li><strong>More jails:</strong> as soon as services with their own login are added (e.g. a web app),
you can activate matching jails – following the same pattern as <code>[sshd]</code>.</li>
<li><strong>Back up <code>jail.local</code>:</strong> your configuration is quickly restored but belongs in the
backup of your server configuration.</li>
<li><strong>Outlook CrowdSec:</strong> Fail2ban protects your host based on <em>your</em> logs. The modern
successor <strong>CrowdSec</strong> later adds <em>collaborative</em> intrusion prevention (shared block
lists) to this – we build it in a dedicated recipe once the reverse proxy is up. To
get started, Fail2ban is exactly right.</li>
</ul>
<p>With that, the security foundation is complete: key login, two firewall layers
(<a href="/en/tutorials/firewall-ufw-setup/">UFW</a> +
<a href="/en/tutorials/netcup-firewall-setup/">netcup firewall</a>),
<a href="/en/tutorials/unattended-upgrades-automatic-updates/">automatic updates</a> and active
brute-force protection. Your server is now not just set up, but solidly secured.</p>
]]></content:encoded></item><item><title>Hardening SSH access</title><link>https://serverkueche.de/en/tutorials/harden-ssh/</link><pubDate>Tue, 14 Jul 2026 00:00:00 +0000</pubDate><author>feedback@serverkueche.de (Serverküche)</author><guid>https://serverkueche.de/en/tutorials/harden-ssh/</guid><description>Key-based login instead of passwords and no root login: the most important moves to harden the front door to your server.</description><content:encoded><![CDATA[<p>After the <a href="/en/tutorials/first-steps-netcup-vps/">initial setup</a> we harden the SSH
access – the most important door to your server.</p>
<h2 id="what-are-we-building">What are we building?</h2>
<p>By the end you log in with an <strong>SSH key</strong> instead of a password, and neither root
login nor password login is possible from outside. This makes the automated
password-guessing attacks that hit every publicly reachable server around the
clock run completely into the void. Tested with <strong>OpenSSH on Debian 13</strong>.</p>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li>A <a href="/en/tutorials/first-steps-netcup-vps/">set-up server</a> with a sudo user</li>
<li>Access to the VNC console in the netcup SCP as a safety line in case you lock yourself out</li>
</ul>
<h2 id="step-by-step">Step by step</h2>
<h3 id="step-1-generate-an-ssh-key-pair">Step 1: Generate an SSH key pair</h3>
<p>If you don&rsquo;t have a key yet, generate an Ed25519 key pair <strong>on your own machine</strong>
(not on the server!):</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ssh-keygen -t ed25519</span></span></code></pre></div>
</div>
<p>You can accept the suggested storage location with Enter. Set a <strong>passphrase</strong> –
it protects the key if your machine falls into the wrong hands. Two files are
created: <code>~/.ssh/id_ed25519</code> (private, stays with you) and
<code>~/.ssh/id_ed25519.pub</code> (public, goes on the server).</p>
<h3 id="step-2-transfer-the-public-key">Step 2: Transfer the public key</h3>
<p><code>ssh-copy-id</code> appends your public key to the <code>~/.ssh/authorized_keys</code> file of your
server user – with the correct file permissions:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ssh-copy-id koch@YOUR_SERVER_IP</span></span></code></pre></div>
</div>
<p>Test right afterwards that key login works:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ssh koch@YOUR_SERVER_IP</span></span></code></pre></div>
</div>
<p>You should now be logged in <strong>without</strong> a server password (only the passphrase of
your key may be requested). Only once that works do we continue – in the next step
we disable password login.</p>
<h3 id="step-3-disable-password-login-and-root-login">Step 3: Disable password login and root login</h3>
<p>Open the SSH server configuration:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo nano /etc/ssh/sshd_config</span></span></code></pre></div>
</div>
<p>Set (or uncomment) these two lines:</p>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">PermitRootLogin no
</span></span><span class="line"><span class="cl">PasswordAuthentication no</span></span></code></pre></div>
</div>
<p><code>PermitRootLogin no</code> blocks direct root login completely,
<code>PasswordAuthentication no</code> allows only key logins.</p>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-amber-400 bg-amber-50 dark:border-amber-700 dark:bg-amber-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">⚠️</span>Warning
  </p>
  <div class="prose-kitchen text-sm">On cloud images there are often extra files under <code>/etc/ssh/sshd_config.d/</code> that
<strong>override</strong> these settings. Check with
<code>grep -r PasswordAuthentication /etc/ssh/sshd_config.d/</code> and adjust any matches as
well.</div>
</div>
<h3 id="step-4-test-the-configuration-and-restart-ssh">Step 4: Test the configuration and restart SSH</h3>
<p>Check the configuration for syntax errors <strong>before</strong> you restart – a typo would
otherwise take the SSH service down:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo sshd -t</span></span></code></pre></div>
</div>
<p>No output means: all good. Then apply it:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">sudo systemctl restart ssh</span></span></code></pre></div>
</div>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-paprika-400 bg-paprika-50 dark:border-paprika-700 dark:bg-paprika-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">🔥</span>Caution
  </p>
  <div class="prose-kitchen text-sm">Test key login now in a <strong>second</strong> terminal session before you close the first –
otherwise you might lock yourself out.</div>
</div>
<p>To verify: <code>sudo systemctl status ssh</code> should show <code>active (running)</code>, and a login
attempt as root must now be rejected with <code>Permission denied (publickey)</code>.</p>
<h2 id="when-things-go-wrong">When things go wrong</h2>
<div class="troubleshoot not-prose">
<p><strong>SSH keeps asking for the password despite <code>ssh-copy-id</code>.</strong> Usually the file
permissions on the server are wrong. SSH ignores <code>authorized_keys</code> if the
permissions are too open: <code>chmod 700 ~/.ssh &amp;&amp; chmod 600 ~/.ssh/authorized_keys</code>.
Also check that you&rsquo;re connecting with the right user (<code>koch@…</code>, not <code>root@…</code>).</p>
<p><strong><code>Permission denied (publickey)</code> – and you can&rsquo;t get in at all anymore.</strong>
Password login was disabled before the key worked. No drama: open the <strong>VNC
console in the netcup SCP</strong>, log in locally there, set <code>PasswordAuthentication yes</code>, restart SSH and start again at step 2.</p>
<p><strong>After the restart the SSH service no longer starts.</strong> Syntax error in the
<code>sshd_config</code> (that&rsquo;s why you always run <code>sshd -t</code> before restarting). Log in via
the VNC console; <code>sudo sshd -t</code> shows you the file and line number of the error.</p>
<p><strong>The settings seem to take effect, but password login still works.</strong> A file under
<code>/etc/ssh/sshd_config.d/</code> (often <code>50-cloud-init.conf</code>) overrides your values.
<code>sudo sshd -T | grep -i passwordauthentication</code> shows the actually effective
setting – adjust matches in the extra files and restart SSH.</p>

</div>

<h2 id="maintenance--backups">Maintenance &amp; backups</h2>
<ul>
<li><strong>Back up the private key:</strong> Without <code>~/.ssh/id_ed25519</code> you can only reach the
server via the VNC console. Back up the key encrypted (e.g. in a password
manager) or add a second key from another device to <code>authorized_keys</code>.</li>
<li><strong>Keep an eye on logins:</strong> <code>sudo journalctl -u ssh --since today</code> shows you all
login attempts. Failed attempts on port 22 are normal and, thanks to the
key requirement, harmless – you can automatically ban such bots later with
<a href="/en/tutorials/fail2ban-setup/">Fail2ban</a>.</li>
<li><strong>Updates:</strong> OpenSSH gets its security updates through the normal <code>apt upgrade</code> –
so keep the server up to date, ideally
<a href="/en/tutorials/unattended-upgrades-automatic-updates/">automated</a>.</li>
</ul>
]]></content:encoded></item><item><title>First steps with a netcup VPS</title><link>https://serverkueche.de/en/tutorials/first-steps-netcup-vps/</link><pubDate>Mon, 13 Jul 2026 00:00:00 +0000</pubDate><author>feedback@serverkueche.de (Serverküche)</author><guid>https://serverkueche.de/en/tutorials/first-steps-netcup-vps/</guid><description>From a freshly ordered netcup server to a ready-to-use system: SSH login, system updates, a sudo user and the most important first steps.</description><content:encoded><![CDATA[<p>You&rsquo;ve ordered your first netcup server – congratulations! Before we fire up the
kitchen, let&rsquo;s set the system up cleanly.</p>
<h2 id="what-are-we-building">What are we building?</h2>
<p>By the end of this tutorial, your VPS runs a current <strong>Debian 13</strong> with all
security updates, and you work with your own user with sudo rights instead of
permanently as <code>root</code>. That&rsquo;s the foundation for all the other recipes in the
Serverküche – from hardening SSH to your first application.</p>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-herb-400 bg-herb-50 dark:border-herb-700 dark:bg-herb-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">🧑‍🍳</span>Tip
  </p>
  <div class="prose-kitchen text-sm">All commands are written for <strong>Debian 13</strong>. On Ubuntu they work almost
identically.</div>
</div>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li>An ordered netcup server with the credentials from your customer account</li>
<li>A terminal on your machine (Linux/macOS) or an SSH client like PuTTY (Windows)</li>
</ul>
<p>Not sure which server size you even need? The
<a href="/en/server-calculator/">server calculator</a> estimates the RAM and CPU you&rsquo;ll need for your
planned services and suggests a matching netcup plan.</p>
<div class="not-prose my-6 overflow-hidden rounded-xl border border-paprika-200 bg-paprika-50 dark:border-paprika-800 dark:bg-paprika-900/20">
  <div class="flex items-center justify-between border-b border-paprika-200 bg-paprika-100 px-4 py-1.5 text-xs font-semibold uppercase tracking-wide text-paprika-700 dark:border-paprika-800 dark:bg-paprika-900/40 dark:text-paprika-300">
    <span>🍳 Recommendation</span>
    <span title="Links marked with * are affiliate links.">Ad</span>
  </div>
  <div class="flex flex-col gap-4 p-4 sm:flex-row sm:items-center sm:justify-between">
    <div>
      <p class="text-lg font-bold text-slate-900 dark:text-white">VPS 1000 G12</p>
      <p class="mt-1 text-sm text-slate-600 dark:text-slate-300">4 vCores · 8 GB RAM · 256 GB NVMe</p>
      <p class="mt-1 text-sm font-semibold text-paprika-700 dark:text-paprika-400">from €10.36/month</p>
      <p class="mt-2 text-sm text-slate-600 dark:text-slate-400">A good start for most self-hosting projects.</p>
    </div>
    <a href="https://www.netcup.com/en/server/vps/vps-1000-g12-iv-12m?ref=44083" rel="sponsored noopener" target="_blank"
   class="inline-flex shrink-0 items-center justify-center rounded-lg bg-paprika-600 px-5 py-2.5 font-semibold text-white transition-colors hover:bg-paprika-700">
  Go to netcup →
</a>

  </div><div class="px-4 pb-4"><p class="not-prose my-3 flex flex-wrap items-center gap-x-2 gap-y-1 rounded-lg border border-herb-500/40 bg-herb-50 px-3 py-2 text-sm text-slate-700 dark:bg-herb-900/20 dark:text-slate-200">
  <span>💶 <strong>5 € voucher</strong> for new netcup customers:</span>
  <code class="rounded bg-white px-2 py-0.5 font-mono text-sm font-semibold text-herb-800 dark:bg-slate-800 dark:text-herb-400">36nc17844976032</code>
  <span class="text-xs text-slate-500 dark:text-slate-400">(new customers only, no domains)</span>
</p></div>
</div>

<h2 id="step-by-step">Step by step</h2>
<h3 id="step-1-log-in-via-ssh">Step 1: Log in via SSH</h3>
<p>Log in with your server&rsquo;s IP address as <code>root</code>. You&rsquo;ll find the IP and the
password in the credentials from your customer account:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ssh root@YOUR_SERVER_IP</span></span></code></pre></div>
</div>
<p>On the very first login, SSH asks whether you want to trust the server:</p>
<div class="sk-code">
  <span class="sk-code-head">Ausgabe</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">The authenticity of host &#39;YOUR_SERVER_IP&#39; can&#39;t be established.
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">Are you sure you want to continue connecting (yes/no/[fingerprint])?</span></span></code></pre></div>
</div>
<p>Answer <code>yes</code> – your machine remembers the server from now on. After that you land
on a prompt like <code>root@v12345:~#</code>.</p>
<h3 id="step-2-update-the-system">Step 2: Update the system</h3>
<p>Freshly delivered images are rarely up to date. So the first thing to do is fetch
all updates – security updates in particular should never wait:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">apt update <span class="o">&amp;&amp;</span> apt upgrade -y</span></span></code></pre></div>
</div>
<p><code>apt update</code> fetches the current package lists, <code>apt upgrade -y</code> installs all
available updates without prompting.</p>
<div class="not-prose my-6 rounded-lg border-l-4 p-4 border-amber-400 bg-amber-50 dark:border-amber-700 dark:bg-amber-900/20">
  <p class="mb-1 flex items-center gap-2 font-semibold text-slate-900 dark:text-white">
    <span aria-hidden="true">⚠️</span>Warning
  </p>
  <div class="prose-kitchen text-sm">Reboot the server after a kernel update (<code>reboot</code>) so the changes take effect. On
a fresh Debian there&rsquo;s no automation for this yet – the safest bet is a reboot
right after the first big update. Debian does <strong>not</strong> create the marker file
<code>/var/run/reboot-required</code> (which indicates a pending reboot) on kernel updates by
default – you&rsquo;ll set up the matching hook later together with
<a href="/en/tutorials/unattended-upgrades-automatic-updates/">automatic updates</a>.</div>
</div>
<h3 id="step-3-create-a-sudo-user">Step 3: Create a sudo user</h3>
<p>Don&rsquo;t work as <code>root</code> permanently: a typo with full rights can wreck the entire
system, and having your own user is the prerequisite for
<a href="/en/tutorials/harden-ssh/">disabling root login completely</a> later. Create a
user – we&rsquo;ll call it <code>koch</code> (&ldquo;cook&rdquo;) here, but you can pick any name:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">adduser koch</span></span></code></pre></div>
</div>
<p>You&rsquo;ll be asked for a password and a few optional details (you can skip the
details with Enter). After that, give the user sudo rights:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">usermod -aG sudo koch</span></span></code></pre></div>
</div>
<h3 id="step-4-test-the-new-user">Step 4: Test the new user</h3>
<p>Check <strong>in a new terminal session</strong> (keep the root session open!) that login and
sudo work:</p>
<div class="sk-code">
  <span class="sk-code-head">Terminal</span>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">ssh koch@YOUR_SERVER_IP
</span></span><span class="line"><span class="cl">sudo whoami</span></span></code></pre></div>
</div>
<p>After entering your password, <code>sudo whoami</code> should return <code>root</code>. From now on you
continue working with this user.</p>
<h2 id="when-things-go-wrong">When things go wrong</h2>
<div class="troubleshoot not-prose">
<p><strong><code>ssh: connect to host … port 22: Connection timed out</code>.</strong> Usually the IP was
mistyped or the server isn&rsquo;t fully provisioned yet. Check the IP in your customer
account and whether the server is shown as &ldquo;online&rdquo; there. Wait a few minutes
after ordering.</p>
<p><strong><code>Permission denied, please try again</code> on root login.</strong> Wrong password – often a
copy-paste issue with invisible trailing spaces, or a different keyboard layout.
Copy the password without surrounding spaces directly from the credentials.</p>
<p><strong><code>WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!</code>.</strong> The server was reinstalled
and has a new host key – SSH rightly raises the alarm. If <strong>you</strong> reinstalled it,
remove the old entry with <code>ssh-keygen -R YOUR_SERVER_IP</code> and reconnect. If you
didn&rsquo;t reinstall, investigate before you connect.</p>
<p><strong><code>sudo: command not found</code> as the new user.</strong> On minimal images the package is
sometimes missing. Install it as <code>root</code>: <code>apt install sudo</code>. Then check that the
user is in the group: <code>groups koch</code> must contain <code>sudo</code> – otherwise repeat step 3
and log out and back in once.</p>

</div>

<h2 id="maintenance--backups">Maintenance &amp; backups</h2>
<ul>
<li><strong>Updates:</strong> You should schedule <code>sudo apt update &amp;&amp; sudo apt upgrade</code> at least
<strong>weekly</strong> – or
<a href="/en/tutorials/unattended-upgrades-automatic-updates/">automate security updates</a>
with <code>unattended-upgrades</code>.</li>
<li><strong>Snapshots:</strong> Create a snapshot in the
<a href="/en/tutorials/netcup-snapshots-scp/">netcup Server Control Panel (SCP)</a> before
bigger changes. It&rsquo;s your safety line while you don&rsquo;t have a proper backup
strategy yet – but a snapshot does <strong>not replace</strong> a backup outside the server.</li>
<li><strong>Credentials:</strong> Keep the root password safe (password manager). Via the VNC
console in the SCP you can still reach the server even when SSH is stuck.</li>
</ul>
<p>You&rsquo;ll find both in the server overview of the SCP: the <strong>VNC console</strong> is under
the <strong>&ldquo;Bildschirm&rdquo; (Display)</strong> tab, and the remaining <strong>snapshot quota</strong> is shown
in the status block.</p>
<p><figure class="my-6"><img src="/en/tutorials/first-steps-netcup-vps/netcup-scp-server_hu_8973a5aa80175a41.webp" srcset="/en/tutorials/first-steps-netcup-vps/netcup-scp-server_hu_ddb4950e5055f484.webp 480w, /en/tutorials/first-steps-netcup-vps/netcup-scp-server_hu_8973a5aa80175a41.webp 768w, /en/tutorials/first-steps-netcup-vps/netcup-scp-server_hu_18e42b31ae963ce5.webp 1200w, /en/tutorials/first-steps-netcup-vps/netcup-scp-server_hu_47fe22d828e0f3e5.webp 1920w" sizes="(min-width: 768px) 768px, 100vw"
    width="768" height="432"
    data-full="/en/tutorials/first-steps-netcup-vps/netcup-scp-server_hu_7cd6ca3ca4c57df9.webp"
    alt="The server overview in the netcup Server Control Panel: tabs including &ldquo;Display&rdquo; (VNC console), status with snapshot quota, and the hardware specs" title="Server overview in the netcup SCP – the Display tab is the VNC console"
    loading="lazy" decoding="async" class="rounded-lg"><figcaption class="mt-2 text-sm text-center text-slate-500 italic">Server overview in the netcup SCP – the Display tab is the VNC console</figcaption></figure></p>
]]></content:encoded></item></channel></rss>