{"slug":"use-ufw-to-lock-down-an-ubuntu-server","title":"Use ufw to lock down an Ubuntu server","tags":["tailscale"],"agent_summary":"Last validated: Jan 5, 2026","trigger_phrases":[],"runnable":false,"markdown":"\r\n# Use ufw to lock down an Ubuntu server\r\n\r\nLast validated: Jan 5, 2026\r\n\r\nAny server on the public internet is bound to be attacked by bots looking\r\nfor weak or leaked passwords and unsafely configured services. Even security\r\nexperts can misconfigure a database, or an unwitting member of the team can\r\naccidentally open up a vulnerability, leaving your devices or network open to\r\nattack.\r\n\r\nIf you have an existing server, you can view this bot traffic by running\r\n`sudo less /var/log/auth.log`. If your server is like many on the web,\r\nyou'll see lots of \"invalid user admin\" or \"invalid user test\".\r\n\r\nTailscale simplifies network security by letting you keep your servers away\r\nfrom the public web, while keeping it easy to connect.\r\n\r\nThe best way to secure a server with Tailscale is to accept connections from\r\nTailscale, and ignore any public internet traffic. Since your Tailscale network\r\nis invisible, except to those in your network, attackers won't even be able to\r\nfind it.\r\n\r\n## [Prerequisites](https://tailscale.com/docs/how-to/secure-ubuntu-server-with-ufw\\#prerequisites)\r\n\r\nBefore you begin this guide, you'll need an Ubuntu server to secure. This guide\r\nassumes you're setting up a [DigitalOcean Ubuntu server](https://www.digitalocean.com/products/linux-distribution/ubuntu),\r\nbut the steps should be similar for most hosting providers and versions of Ubuntu.\r\n\r\nYou'll also need a Tailscale network, known as a tailnet. For information about creating a tailnet, refer to the [Tailscale quickstart](https://tailscale.com/docs/how-to/quickstart).\r\n\r\nNext, you'll need to install the Tailscale client on your local machine and log in.\r\n\r\n[Download Tailscale](https://tailscale.com/download)\r\n\r\nWe'll follow the same steps on the Ubuntu server next.\r\n\r\n## [Step 1: SSH into your new Ubuntu server](https://tailscale.com/docs/how-to/secure-ubuntu-server-with-ufw\\#step-1-ssh-into-your-new-ubuntu-server)\r\n\r\nAfter spinning up a new server, SSH into it with your account details.\r\n\r\n```shell\r\nssh <username>@<server host ip>\r\n```\r\n\r\n## [Step 2: Install Tailscale on your Ubuntu server](https://tailscale.com/docs/how-to/secure-ubuntu-server-with-ufw\\#step-2-install-tailscale-on-your-ubuntu-server)\r\n\r\n1. Install Tailscale using the one-line script below, or read our [detailed install instructions for Ubuntu](https://tailscale.com/download/linux)\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```shell\r\ncurl -fsSL https://tailscale.com/install.sh | sh\r\n```\r\n\r\n2. Authenticate and connect your machine to\r\nyour Tailscale network\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n```shell\r\nsudo tailscale up\r\n```\r\n\r\n3. (Optional) If you signed in with a custom domain (not a `@gmail.com` address) visit the [admin console](https://login.tailscale.com/admin) and authorize your new endpoint.\r\n\r\n4. (Optional) Disable key expiry for this server\r\n\r\n\r\nAs a security feature, Tailscale requires periodic reauthentication. To prevent getting locked out, you may want to disable expiry on certain endpoints, such as this trusted server. Disable key expiry by following [these instructions](https://tailscale.com/docs/features/access-control/key-expiry).\r\n\r\nIf you leave key expiry on, be familiar with how to regain server access. For example, DigitalOcean provides access via a [droplet console](https://www.digitalocean.com/docs/droplets/resources/console).\r\n\r\n## [Step 3: SSH over Tailscale](https://tailscale.com/docs/how-to/secure-ubuntu-server-with-ufw\\#step-3-ssh-over-tailscale)\r\n\r\nAn important step — since we're about to restrict SSH access to be only over\r\nTailscale, we'll exit the machine and re-SSH with our Tailscale IP.\r\n\r\nFirst, [find and copy your machine's Tailscale IP](https://tailscale.com/docs/concepts/ip-and-dns-addresses). The\r\neasiest way to do this is to run\r\n\r\n```shell\r\ntailscale ip -4\r\n```\r\n\r\nAnd copy the 100.x.y.z shown.\r\n\r\nOnce you've found it, `exit` your SSH session, and start a new one with your\r\nnewly copied Tailscale IP.\r\n\r\n```shell\r\nssh <username>@<copied 100.x.y.z address>\r\n```\r\n\r\n## [Step 4: Enable ufw](https://tailscale.com/docs/how-to/secure-ubuntu-server-with-ufw\\#step-4-enable-ufw)\r\n\r\nFor this guide, we'll use [ufw](https://help.ubuntu.com/community/UFW)\r\n(Uncomplicated Firewall) to restrict non-Tailscale traffic to our server. It comes pre-installed on Ubuntu 18.04, so no installation is needed.\r\n\r\nBefore we continue editing rules, you'll need to enable ufw if it isn't already enabled.\r\n\r\n```shell\r\nsudo ufw enable\r\n```\r\n\r\n## [Step 5: Restrict all other traffic](https://tailscale.com/docs/how-to/secure-ubuntu-server-with-ufw\\#step-5-restrict-all-other-traffic)\r\n\r\nNext, we'll set up rules to reject all incoming non-Tailscale traffic, and allow all outgoing\r\ntraffic by default.\r\n\r\n```shell\r\nsudo ufw default deny incoming\r\nsudo ufw default allow outgoing\r\n```\r\n\r\nNow that we've set these defaults check your existing firewall rules you might\r\nneed to keep.\r\n\r\n```shell\r\nsudo ufw status verbose\r\n```\r\n\r\nThe output might display a list of firewall rules, like this:\r\n\r\n```shell\r\nTo                          Action      From\r\n--                          ------      ----\r\n22/tcp                      ALLOW IN    Anywhere\r\n80/tcp                      ALLOW IN    Anywhere\r\n443/tcp                     ALLOW IN    Anywhere\r\nAnywhere on tailscale0      ALLOW IN    Anywhere\r\n22/tcp (v6)                 ALLOW IN    Anywhere (v6)\r\n80/tcp (v6)                 ALLOW IN    Anywhere (v6)\r\n443/tcp (v6)                ALLOW IN    Anywhere (v6)\r\nAnywhere (v6) on tailscale0 ALLOW IN    Anywhere (v6)\r\n```\r\n\r\nIf the output does not include an `Anywhere on tailscale0` rule, you can create one manually:\r\n\r\n```shell\r\nsudo ufw allow in on tailscale0\r\n```\r\n\r\nAll other connections are denied by default and so not listed above. We want to limit this list to the minimum set needed.\r\n\r\nTo completely lock down your server while retaining SSH access, you could delete\r\nevery rule except for the \"Anywhere on tailscale0\" rule.\r\n\r\nFor the example above, we'll delete all \"22/tcp\" rules, which will remove the\r\nability to SSH over regular connections:\r\n\r\n```shell\r\nsudo ufw delete 22/tcp\r\n```\r\n\r\nNow, only \"Anywhere on tailscale0\" remains, meaning SSH can only occur over Tailscale.\r\n\r\n```shell\r\nTo                          Action      From\r\n--                          ------      ----\r\n80/tcp                      ALLOW       Anywhere\r\n443/tcp                     ALLOW       Anywhere\r\nAnywhere on tailscale0      ALLOW IN    Anywhere\r\n80/tcp (v6)                 ALLOW       Anywhere (v6)\r\n443/tcp (v6)                ALLOW       Anywhere (v6)\r\nAnywhere (v6) on tailscale0 ALLOW IN    Anywhere (v6)\r\n```\r\n\r\nIf you expose a public web service (80/tcp, 443/tcp), you'll want to keep those rules around. For less public services like FTP (21/tcp) or a database, consider connecting devices that rely on those services over Tailscale too.\r\n\r\nThis guide assumes SSH is running on the default port, port 22. If you've changed\r\nyour SSH port, you may need to change these instructions as well.\r\n\r\n## [Step 6: Restart ufw and SSH](https://tailscale.com/docs/how-to/secure-ubuntu-server-with-ufw\\#step-6-restart-ufw-and-ssh)\r\n\r\nOnce you've set up firewall rules to restrict all non-Tailscale connections,\r\nrestart ufw and SSH\r\n\r\n```shell\r\nsudo ufw reload\r\nsudo service ssh restart\r\n```\r\n\r\nNow your server will ignore any SSH requests that don't come from users authenticated\r\nto your private Tailscale network.\r\n\r\n## [Step 7: Test and verify](https://tailscale.com/docs/how-to/secure-ubuntu-server-with-ufw\\#step-7-test-and-verify)\r\n\r\nLet's make sure that everything is working as expected.\r\n\r\nFirst, let's `exit` the existing SSH session.\r\n\r\nThen, let's try to connect with the public IP address from earlier.\r\n\r\nThe connection attempt fails and the operation times out.\r\n\r\n```shell\r\nssh <username>@<server host ip>\r\nssh: connect to host <server host ip> port 22: Operation timed out\r\n```\r\n\r\nNow, let's try to SSH in using the Tailscale IP address (starting with 100.x.y.z) from earlier.\r\n\r\n```shell\r\nssh <username>@<copied 100.x.y.z address>\r\n```\r\n\r\nEverything now works as expected. Type `exit` to close the SSH connection again.\r\n\r\nThis time, quit the Tailscale client on your local machine.\r\n\r\nIf you try to `ssh` to the Ubuntu server again, the operation times out and we are no longer able to connect.\r\n\r\n```shell\r\nssh <username>@<copied 100.x.y.z address>\r\nssh: connect to host <copied 100.x.y.z address> port 22: Operation timed out\r\n```\r\n\r\nWe've now verified that we can only connect when we're successfully authenticated to the Tailscale client running on our local machine.\r\n\r\n## [(Optional) enable multifactor authentication (MFA) for all SSH connections](https://tailscale.com/docs/how-to/secure-ubuntu-server-with-ufw\\#optional-enable-multifactor-authentication-mfa-for-all-ssh-connections)\r\n\r\nNow that your server can only be accessed by using Tailscale, you can enforce login\r\nrules in using your Tailscale network's [identity provider](https://tailscale.com/docs/integrations/identity),\r\nknowing they will apply to all your SSH connections too.\r\n\r\nFor example, you may want to configure your identity provider to [require multifactor authentication (MFA)](https://tailscale.com/docs/multifactor-auth) for every sign-in.\r\n\r\nThanks to _/u/mgozmovies_ whose experimentation and\r\n[write-up on /r/tailscale](https://old.reddit.com/r/Tailscale/comments/hwnc0l/restricting_ssh_access_to_tailscale_interface_on)\r\ninspired this topic.\r\n\r\n![Project Logo](https://cdn.brandfetch.io/tailscale.com/fallback/lettermark/theme/dark/h/256/w/256/icon?c=1bfwsmEH20zzEfSNTed)\r\n\r\nAsk AI\r\n\r\nreCAPTCHA\r\n\r\nRecaptcha requires verification.\r\n\r\nprotected by **reCAPTCHA**\r\n","html":"<h1>Use ufw to lock down an Ubuntu server</h1>\n<p>Last validated: Jan 5, 2026</p>\n<p>Any server on the public internet is bound to be attacked by bots looking\r\nfor weak or leaked passwords and unsafely configured services. Even security\r\nexperts can misconfigure a database, or an unwitting member of the team can\r\naccidentally open up a vulnerability, leaving your devices or network open to\r\nattack.</p>\n<p>If you have an existing server, you can view this bot traffic by running\r\n<code>sudo less /var/log/auth.log</code>. If your server is like many on the web,\r\nyou'll see lots of \"invalid user admin\" or \"invalid user test\".</p>\n<p>Tailscale simplifies network security by letting you keep your servers away\r\nfrom the public web, while keeping it easy to connect.</p>\n<p>The best way to secure a server with Tailscale is to accept connections from\r\nTailscale, and ignore any public internet traffic. Since your Tailscale network\r\nis invisible, except to those in your network, attackers won't even be able to\r\nfind it.</p>\n<h2><a href=\"https://tailscale.com/docs/how-to/secure-ubuntu-server-with-ufw#prerequisites\">Prerequisites</a></h2>\n<p>Before you begin this guide, you'll need an Ubuntu server to secure. This guide\r\nassumes you're setting up a <a href=\"https://www.digitalocean.com/products/linux-distribution/ubuntu\">DigitalOcean Ubuntu server</a>,\r\nbut the steps should be similar for most hosting providers and versions of Ubuntu.</p>\n<p>You'll also need a Tailscale network, known as a tailnet. For information about creating a tailnet, refer to the <a href=\"https://tailscale.com/docs/how-to/quickstart\">Tailscale quickstart</a>.</p>\n<p>Next, you'll need to install the Tailscale client on your local machine and log in.</p>\n<p><a href=\"https://tailscale.com/download\">Download Tailscale</a></p>\n<p>We'll follow the same steps on the Ubuntu server next.</p>\n<h2><a href=\"https://tailscale.com/docs/how-to/secure-ubuntu-server-with-ufw#step-1-ssh-into-your-new-ubuntu-server\">Step 1: SSH into your new Ubuntu server</a></h2>\n<p>After spinning up a new server, SSH into it with your account details.</p>\n<pre><code class=\"language-shell\">ssh &#x3C;username>@&#x3C;server host ip>\n</code></pre>\n<h2><a href=\"https://tailscale.com/docs/how-to/secure-ubuntu-server-with-ufw#step-2-install-tailscale-on-your-ubuntu-server\">Step 2: Install Tailscale on your Ubuntu server</a></h2>\n<ol>\n<li>Install Tailscale using the one-line script below, or read our <a href=\"https://tailscale.com/download/linux\">detailed install instructions for Ubuntu</a></li>\n</ol>\n<pre><code class=\"language-shell\">curl -fsSL https://tailscale.com/install.sh | sh\n</code></pre>\n<ol start=\"2\">\n<li>Authenticate and connect your machine to\r\nyour Tailscale network</li>\n</ol>\n<pre><code class=\"language-shell\">sudo tailscale up\n</code></pre>\n<ol start=\"3\">\n<li>\n<p>(Optional) If you signed in with a custom domain (not a <code>@gmail.com</code> address) visit the <a href=\"https://login.tailscale.com/admin\">admin console</a> and authorize your new endpoint.</p>\n</li>\n<li>\n<p>(Optional) Disable key expiry for this server</p>\n</li>\n</ol>\n<p>As a security feature, Tailscale requires periodic reauthentication. To prevent getting locked out, you may want to disable expiry on certain endpoints, such as this trusted server. Disable key expiry by following <a href=\"https://tailscale.com/docs/features/access-control/key-expiry\">these instructions</a>.</p>\n<p>If you leave key expiry on, be familiar with how to regain server access. For example, DigitalOcean provides access via a <a href=\"https://www.digitalocean.com/docs/droplets/resources/console\">droplet console</a>.</p>\n<h2><a href=\"https://tailscale.com/docs/how-to/secure-ubuntu-server-with-ufw#step-3-ssh-over-tailscale\">Step 3: SSH over Tailscale</a></h2>\n<p>An important step — since we're about to restrict SSH access to be only over\r\nTailscale, we'll exit the machine and re-SSH with our Tailscale IP.</p>\n<p>First, <a href=\"https://tailscale.com/docs/concepts/ip-and-dns-addresses\">find and copy your machine's Tailscale IP</a>. The\r\neasiest way to do this is to run</p>\n<pre><code class=\"language-shell\">tailscale ip -4\n</code></pre>\n<p>And copy the 100.x.y.z shown.</p>\n<p>Once you've found it, <code>exit</code> your SSH session, and start a new one with your\r\nnewly copied Tailscale IP.</p>\n<pre><code class=\"language-shell\">ssh &#x3C;username>@&#x3C;copied 100.x.y.z address>\n</code></pre>\n<h2><a href=\"https://tailscale.com/docs/how-to/secure-ubuntu-server-with-ufw#step-4-enable-ufw\">Step 4: Enable ufw</a></h2>\n<p>For this guide, we'll use <a href=\"https://help.ubuntu.com/community/UFW\">ufw</a>\r\n(Uncomplicated Firewall) to restrict non-Tailscale traffic to our server. It comes pre-installed on Ubuntu 18.04, so no installation is needed.</p>\n<p>Before we continue editing rules, you'll need to enable ufw if it isn't already enabled.</p>\n<pre><code class=\"language-shell\">sudo ufw enable\n</code></pre>\n<h2><a href=\"https://tailscale.com/docs/how-to/secure-ubuntu-server-with-ufw#step-5-restrict-all-other-traffic\">Step 5: Restrict all other traffic</a></h2>\n<p>Next, we'll set up rules to reject all incoming non-Tailscale traffic, and allow all outgoing\r\ntraffic by default.</p>\n<pre><code class=\"language-shell\">sudo ufw default deny incoming\r\nsudo ufw default allow outgoing\n</code></pre>\n<p>Now that we've set these defaults check your existing firewall rules you might\r\nneed to keep.</p>\n<pre><code class=\"language-shell\">sudo ufw status verbose\n</code></pre>\n<p>The output might display a list of firewall rules, like this:</p>\n<pre><code class=\"language-shell\">To                          Action      From\r\n--                          ------      ----\r\n22/tcp                      ALLOW IN    Anywhere\r\n80/tcp                      ALLOW IN    Anywhere\r\n443/tcp                     ALLOW IN    Anywhere\r\nAnywhere on tailscale0      ALLOW IN    Anywhere\r\n22/tcp (v6)                 ALLOW IN    Anywhere (v6)\r\n80/tcp (v6)                 ALLOW IN    Anywhere (v6)\r\n443/tcp (v6)                ALLOW IN    Anywhere (v6)\r\nAnywhere (v6) on tailscale0 ALLOW IN    Anywhere (v6)\n</code></pre>\n<p>If the output does not include an <code>Anywhere on tailscale0</code> rule, you can create one manually:</p>\n<pre><code class=\"language-shell\">sudo ufw allow in on tailscale0\n</code></pre>\n<p>All other connections are denied by default and so not listed above. We want to limit this list to the minimum set needed.</p>\n<p>To completely lock down your server while retaining SSH access, you could delete\r\nevery rule except for the \"Anywhere on tailscale0\" rule.</p>\n<p>For the example above, we'll delete all \"22/tcp\" rules, which will remove the\r\nability to SSH over regular connections:</p>\n<pre><code class=\"language-shell\">sudo ufw delete 22/tcp\n</code></pre>\n<p>Now, only \"Anywhere on tailscale0\" remains, meaning SSH can only occur over Tailscale.</p>\n<pre><code class=\"language-shell\">To                          Action      From\r\n--                          ------      ----\r\n80/tcp                      ALLOW       Anywhere\r\n443/tcp                     ALLOW       Anywhere\r\nAnywhere on tailscale0      ALLOW IN    Anywhere\r\n80/tcp (v6)                 ALLOW       Anywhere (v6)\r\n443/tcp (v6)                ALLOW       Anywhere (v6)\r\nAnywhere (v6) on tailscale0 ALLOW IN    Anywhere (v6)\n</code></pre>\n<p>If you expose a public web service (80/tcp, 443/tcp), you'll want to keep those rules around. For less public services like FTP (21/tcp) or a database, consider connecting devices that rely on those services over Tailscale too.</p>\n<p>This guide assumes SSH is running on the default port, port 22. If you've changed\r\nyour SSH port, you may need to change these instructions as well.</p>\n<h2><a href=\"https://tailscale.com/docs/how-to/secure-ubuntu-server-with-ufw#step-6-restart-ufw-and-ssh\">Step 6: Restart ufw and SSH</a></h2>\n<p>Once you've set up firewall rules to restrict all non-Tailscale connections,\r\nrestart ufw and SSH</p>\n<pre><code class=\"language-shell\">sudo ufw reload\r\nsudo service ssh restart\n</code></pre>\n<p>Now your server will ignore any SSH requests that don't come from users authenticated\r\nto your private Tailscale network.</p>\n<h2><a href=\"https://tailscale.com/docs/how-to/secure-ubuntu-server-with-ufw#step-7-test-and-verify\">Step 7: Test and verify</a></h2>\n<p>Let's make sure that everything is working as expected.</p>\n<p>First, let's <code>exit</code> the existing SSH session.</p>\n<p>Then, let's try to connect with the public IP address from earlier.</p>\n<p>The connection attempt fails and the operation times out.</p>\n<pre><code class=\"language-shell\">ssh &#x3C;username>@&#x3C;server host ip>\r\nssh: connect to host &#x3C;server host ip> port 22: Operation timed out\n</code></pre>\n<p>Now, let's try to SSH in using the Tailscale IP address (starting with 100.x.y.z) from earlier.</p>\n<pre><code class=\"language-shell\">ssh &#x3C;username>@&#x3C;copied 100.x.y.z address>\n</code></pre>\n<p>Everything now works as expected. Type <code>exit</code> to close the SSH connection again.</p>\n<p>This time, quit the Tailscale client on your local machine.</p>\n<p>If you try to <code>ssh</code> to the Ubuntu server again, the operation times out and we are no longer able to connect.</p>\n<pre><code class=\"language-shell\">ssh &#x3C;username>@&#x3C;copied 100.x.y.z address>\r\nssh: connect to host &#x3C;copied 100.x.y.z address> port 22: Operation timed out\n</code></pre>\n<p>We've now verified that we can only connect when we're successfully authenticated to the Tailscale client running on our local machine.</p>\n<h2><a href=\"https://tailscale.com/docs/how-to/secure-ubuntu-server-with-ufw#optional-enable-multifactor-authentication-mfa-for-all-ssh-connections\">(Optional) enable multifactor authentication (MFA) for all SSH connections</a></h2>\n<p>Now that your server can only be accessed by using Tailscale, you can enforce login\r\nrules in using your Tailscale network's <a href=\"https://tailscale.com/docs/integrations/identity\">identity provider</a>,\r\nknowing they will apply to all your SSH connections too.</p>\n<p>For example, you may want to configure your identity provider to <a href=\"https://tailscale.com/docs/multifactor-auth\">require multifactor authentication (MFA)</a> for every sign-in.</p>\n<p>Thanks to <em>/u/mgozmovies</em> whose experimentation and\r\n<a href=\"https://old.reddit.com/r/Tailscale/comments/hwnc0l/restricting_ssh_access_to_tailscale_interface_on\">write-up on /r/tailscale</a>\r\ninspired this topic.</p>\n<p><img src=\"https://cdn.brandfetch.io/tailscale.com/fallback/lettermark/theme/dark/h/256/w/256/icon?c=1bfwsmEH20zzEfSNTed\" alt=\"Project Logo\"></p>\n<p>Ask AI</p>\n<p>reCAPTCHA</p>\n<p>Recaptcha requires verification.</p>\n<p>protected by <strong>reCAPTCHA</strong></p>\n"}