{"id":2379,"date":"2021-09-04T19:50:01","date_gmt":"2021-09-04T19:50:01","guid":{"rendered":"https:\/\/dft.wiki\/?p=2379"},"modified":"2026-06-08T22:29:46","modified_gmt":"2026-06-09T02:29:46","slug":"hardening-openssh-with-2fa-on-ubuntu-20-04","status":"publish","type":"post","link":"https:\/\/dft.wiki\/?p=2379","title":{"rendered":"Hardening OpenSSH with 2FA on Ubuntu"},"content":{"rendered":"<p>This setup requires the Google Authenticator app to generate OTPs (One-Time Passwords).<\/p>\n<ul>\n<li>Android [<a href=\"https:\/\/play.google.com\/store\/apps\/details?id=com.google.android.apps.authenticator2\">Link<\/a>]<\/li>\n<li>iOS [<a href=\"https:\/\/itunes.apple.com\/us\/app\/google-authenticator\/id388497605\">Link<\/a>]<\/li>\n<\/ul>\n<pre>sudo apt install libpam-google-authenticator -y\r\nsudo nano \/etc\/pam.d\/sshd<\/pre>\n<p>Append the following:<\/p>\n<pre>auth required pam_google_authenticator.so <strong>nullok<\/strong><\/pre>\n<p>The <strong>nullok<\/strong> option allows users to log in even if they have not set up 2FA yet.<\/p>\n<p>Edit the OpenSSH server configuration:<\/p>\n<pre>sudo nano \/etc\/ssh\/sshd_config<\/pre>\n<p>Update these options:<\/p>\n<pre>ChallengeResponseAuthentication yes\r\nPasswordAuthentication no\r\nAuthenticationMethods publickey,keyboard-interactive<\/pre>\n<p>Make sure you have SSH key access before setting <strong>PasswordAuthentication<\/strong> to <strong>no<\/strong>.<\/p>\n<p>To enable 2FA for <strong>sudo<\/strong> instead (do NOT combine 2FA for SSH and sudo; use one or the other):<\/p>\n<pre>sudo nano \/etc\/pam.d\/common-auth<\/pre>\n<p>Append:<\/p>\n<pre>auth required pam_google_authenticator.so <strong>nullok<\/strong>\r\nauth required pam_permit.so<\/pre>\n<p>Run the authenticator to generate a QR code for the current user:<\/p>\n<pre>google-authenticator<\/pre>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-2380 aligncenter\" src=\"https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2021\/09\/Screenshot_2021-09-04_15-27-26.png\" alt=\"\" width=\"701\" height=\"1188\" srcset=\"https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2021\/09\/Screenshot_2021-09-04_15-27-26.png 701w, https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2021\/09\/Screenshot_2021-09-04_15-27-26-177x300.png 177w, https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2021\/09\/Screenshot_2021-09-04_15-27-26-604x1024.png 604w\" sizes=\"auto, (max-width: 701px) 100vw, 701px\" \/><\/p>\n<p>Restart the SSH service, then test your login from a separate terminal to avoid being locked out:<\/p>\n<pre>sudo systemctl restart sshd.service<\/pre>\n<p>Note: a new file <strong>~\/.google_authenticator<\/strong> is created during setup. It can be backed up or copied to other servers you want to access with the same token.<\/p>\n<p>Once all users have set up Google Authenticator, edit <strong>\/etc\/pam.d\/sshd<\/strong> and remove <strong>nullok<\/strong>, leaving:<\/p>\n<pre>auth required pam_google_authenticator.so<\/pre>\n<p>To recover access to a user account, read the first line of their configuration file as root and enter it into the user&#8217;s Google Authenticator app:<\/p>\n<pre>head -n 1 \/home\/<strong>user<\/strong>\/.google_authenticator<\/pre>\n<p>If that is not possible, delete the configuration file and ensure the <strong>nullok<\/strong> option is enabled. The user will then be able to log in without 2FA and generate a new QR code:<\/p>\n<pre>google-authenticator <strong>-t -d -f -r <span class=\"token number\">3<\/span> -R <span class=\"token number\">30<\/span> -W<\/strong><\/pre>\n<p>Note: the arguments <strong>-t -d -f -r <span class=\"token number\">3<\/span> -R <span class=\"token number\">30<\/span> -W<\/strong> are optional and enable non-interactive setup.<\/p>\n<p>To prompt a user to set up 2FA on their next login, create the following file in their home directory:<\/p>\n<pre>sudo nano \/home\/<strong>user<\/strong>\/.bash_login<\/pre>\n<p>Paste the following content:<\/p>\n<pre>#!\/bin\/bash\r\nFILE=~\/.google_authenticator\r\nif [ ! -f \"$FILE\" ]; then\r\necho \"\"\r\necho \"|-------------------------------------------------------------------------------------------------|\"\r\necho \"| Download the Google Authenticator app on your smartphone and scan the following QR code: |\"\r\necho \"|-------------------------------------------------------------------------------------------------|\"\r\necho \"\"\r\ngoogle-authenticator -t -d -f -r 3 -R 30 -W\r\nfi<\/pre>\n<p>To include this script in every new user&#8217;s home directory automatically, copy it to:<\/p>\n<pre>sudo cp .bash_login \/etc\/skel\/.bash_login<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>This setup requires the Google Authenticator app to generate OTPs (One-Time Passwords). Android [Link] iOS [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4,6],"tags":[],"class_list":["post-2379","post","type-post","status-publish","format-standard","hentry","category-linux","category-raspberry-pi"],"_links":{"self":[{"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/posts\/2379","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/dft.wiki\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2379"}],"version-history":[{"count":8,"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/posts\/2379\/revisions"}],"predecessor-version":[{"id":5714,"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/posts\/2379\/revisions\/5714"}],"wp:attachment":[{"href":"https:\/\/dft.wiki\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2379"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dft.wiki\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2379"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dft.wiki\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2379"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}