IPFW, nginx and Let’s Encrypt on FreeBSD-13.0-RELEASE

This post is intended to document the process of setting up a basic webserver based on a default install of FreeBSD on vultr. That said, this guide should be helpful regardless of if you’re with another hosting provider or on your own hardware.

I’m assuming you have some basic familiarity with a UNIX shell, vi(1) and know how to set up your own (sub)domains and DNS settings.

Patching the system

First things first, let’s ssh into our new vultr server:

$ ssh root@test.ultros.pro

The first thing we’ll want to do is install any security patches for the base system, as well as upgrade our third-party packages:

root@test:~ # freebsd-update fetch install
root@test:~ # pkg upgrade
root@test:~ # reboot

The reboot is possibly skippable, depending on if you need to boot into a new kernel. You probably do though.

Adding a non-root user and configuring sshd(8)

Let’s ssh back into our system and set the root password:

$ ssh root@test.ultros.pro
root@test:~ # passwd
Changing local password for root
New Password:
Retype New Password:

Next let’s add a non-root user, you can choose whatever you like for this section, but make sure that your new user is in the ‘wheel’ group (so we can use the su(1) command) and I’d recommend setting their login class to ‘staff’. I also like to use tcsh(1) and disable password authentication but that’s up to you.

root@test:~ # adduser
Username: ultros
Full name: ULTROS_PROFESSIONAL
Uid (Leave empty for default): 
Login group [ultros]: 
Login group is ultros. Invite ultros into other groups? []: wheel
Login class [default]: staff
Shell (sh csh tcsh nologin) [sh]: tcsh
Home directory [/home/ultros]: 
Home directory permissions (Leave empty for default): 
Use password-based authentication? [yes]: no
Lock out the account after creation? [no]: 
Username   : ultros
Password   : <disabled>
Full Name  : ULTROS_PROFESSIONAL
Uid        : 1001
Class      : staff
Groups     : ultros wheel
Home       : /home/ultros
Home Mode  : 
Shell      : /bin/tcsh
Locked     : no
OK? (yes/no): yes
adduser: INFO: Successfully added (ultros) to the user database.
Add another user? (yes/no): no
Goodbye!

Next let’s copy over our public ssh key from our local machine to the new server:

$ scp ~/.ssh root@test.ultros.pro:~
root@test:~ # mkdir ~ultros/.ssh
root@test:~ # cat id_rsa.pub > ~ultros/.ssh/authorized_keys
root@test:~ # chown -R ultros:ultros ~ultros/.ssh/
root@test:~ # chmod 600 ~ultros/.ssh/authorized_keys

We should now be able to login to the server as our non-root user, let’s test if we can gain root access using su(1):

$ ssh ultros@test.ultros.pro
ultros@test:~ % su
Password:
# 

Great, now let’s lock down our sshd(8) config, basically we want to disable pam(3) authentication, root logins and password authentication. On vultr the latter two settings are the final two lines of the file:

# vi /etc/ssh/sshd_config

--- /etc/ssh/sshd_config.orig	2021-10-11 06:37:45.840510000 +0000
+++ /etc/ssh/sshd_config	2021-10-11 06:38:14.724397000 +0000
@@ -83,7 +83,7 @@
 # If you just want the PAM account and session checks to run without
 # PAM authentication, then enable this but set PasswordAuthentication
 # and ChallengeResponseAuthentication to 'no'.
-#UsePAM yes
+UsePAM no
 
 #AllowAgentForwarding yes
 #AllowTcpForwarding yes
@@ -119,6 +119,3 @@
 #	AllowTcpForwarding no
 #	PermitTTY no
 #	ForceCommand cvs server
-
-PermitRootLogin yes
-PasswordAuthentication yes

Next let’s restart sshd(8):

# service sshd restart
Performing sanity check on sshd configuration.
Stopping sshd.
Performing sanity check on sshd configuration.
Starting sshd.

On your local machine, check that you can no longer log in as root, and that you can log in as your new user:

$ ssh root@test.ultros.pro
root@test.ultros.pro: Permission denied (publickey,keyboard-interactive).
$ ssh ultros@test.ultros.pro
ultros@test:~ % 

Great job, now you can only log into your server via public key authentication to a non-root user!

Configuring ifpw(8)

Now that we have secure logins, let’s make sure that only the ports we want open for our server are available to the public. To do this we’ll use the built in ipfw(8) firewall:

ultros@test:~ % su
Password:
# sysrc firewall_enable="YES"
firewall_enable: NO -> YES
# sysrc firewall_quiet="YES"
firewall_quiet: NO -> YES
# sysrc firewall_type="workstation"
firewall_type: UNKNOWN -> workstation
# sysrc firewall_myservices="22/tcp 80/tcp 443/tcp"
firewall_myservices:  -> 22/tcp 80/tcp 443/tcp
# sysrc firewall_allowservices="any"
firewall_allowservices:  -> any
# sysrc firewall_logdeny="YES"
firewall_logdeny: NO -> YES

These options, respectively:

  1. Enable the firewall
  2. Disable shell prompts
  3. Configure the firewall to apply rules for only this machine (we’re not routing traffic after all)
  4. Define the ports we want to open
  5. Specify that anyone in the world is allowed to use these ports
  6. Log all denials of access

These can all be updated later in the /etc/rc.conf file.

Now let’s start the firewall:

# service ipfw start
Firewall rules loaded.

If we can still ssh in from our local machine we’ve done everything right:

$ ssh ultros@test.ultros.pro
ultros@test:~ % 

Awesome, our firewall is working!

Installing nginx and configuring certbot

Okay, now things are starting to pick up, let’s get our web server running!

ultros@test:~ % su
Password:
# pkg install nginx
# sysrc nginx_enable="YES"
nginx_enable:  -> YES
# service nginx start
Performing sanity check on nginx configuration:
nginx: the configuration file /usr/local/etc/nginx/nginx.conf syntax is ok
nginx: configuration file /usr/local/etc/nginx/nginx.conf test is successful
Starting nginx.

We’ll have to set the server_name attribute in our nginx conifguration for the next step to work:

# vi /usr/local/etc/nginx/nginx.conf
--- /usr/local/etc/nginx/nginx.conf.orig	2021-10-11 07:16:41.203904000 +0000
+++ /usr/local/etc/nginx/nginx.conf	2021-10-11 07:16:55.844653000 +0000
@@ -39,7 +39,7 @@
 
     server {
         listen       80;
-        server_name  localhost;
+        server_name  test.ultros.pro;
 
         #charset koi8-r;

Let’s reload the nginx configuration:

# service nginx reload
Performing sanity check on nginx configuration:
nginx: the configuration file /usr/local/etc/nginx/nginx.conf syntax is ok
nginx: configuration file /usr/local/etc/nginx/nginx.conf test is successful

Now we can open a browser and head to our new web page, it should look something like this (don’t worry if you get warnings about an insecure connection, we’ll set up HTTPS next):

Cool! Now let’s get an SSL certificate:

# pkg install py38-certbot-nginx
# certbot
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Enter email address (used for urgent renewal and security notices)
 (Enter 'c' to cancel): noise@ultros.pro

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf. You must
agree in order to register with the ACME server. Do you agree?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: y

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Would you be willing, once your first certificate is successfully issued, to
share your email address with the Electronic Frontier Foundation, a founding
partner of the Let's Encrypt project and the non-profit organization that
develops Certbot? We'd like to send you email about our work encrypting the web,
EFF news, campaigns, and ways to support digital freedom.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: n
Account registered.

Which names would you like to activate HTTPS for?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: test.ultros.pro
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate numbers separated by commas and/or spaces, or leave input
blank to select all options shown (Enter 'c' to cancel): 1
Requesting a certificate for test.ultros.pro

Successfully received certificate.
Certificate is saved at: /usr/local/etc/letsencrypt/live/test.ultros.pro/fullchain.pem
Key is saved at:         /usr/local/etc/letsencrypt/live/test.ultros.pro/privkey.pem
This certificate expires on 2022-01-09.
These files will be updated when the certificate renews.

Deploying certificate
Successfully deployed certificate for test.ultros.pro to /usr/local/etc/nginx/nginx.conf
Congratulations! You have successfully enabled HTTPS on https://test.ultros.pro

Now let’s check our ssl settings at https://www.ssllabs.com/ssltest/index.html:

Pretty good!

Last thing is to make sure we can get our cert renewed regularly, we do this by creating a cron(8) job:

# crontab -e

0       0,12    *       *       *       root /usr/local/bin/python -c 'import random; import time; time.sleep(random.random() * 3600)' && /usr/local/bin/certbot renew -q

Congratulations! Your FreeBSD server is now set up to serve pages over HTTPS!

Leave a comment