<-- All Articles

Why HTTPS on FreeBSD

Every production web server needs HTTPS. Search engines penalize unencrypted sites, browsers display security warnings to visitors, and sensitive data travels in plaintext without TLS. FreeBSD's security-first philosophy makes it an excellent platform for hardened web servers, and properly configured SSL/TLS is the foundation of that security posture. The goal is an A+ rating on SSL Labs -- and with FreeBSD and Nginx, that is straightforward to achieve.

Let's Encrypt provides free, domain-validated certificates that are trusted by every major browser. Combined with automated renewal, there is no reason to run an unencrypted FreeBSD web server in production.

Prerequisites

Before starting, you need a FreeBSD server with Nginx installed and a domain name pointing to the server's IP address. Install Nginx from packages if you have not already:

pkg install nginx
sysrc nginx_enable=YES
service nginx start

Ports 80 and 443 must be open in your pf firewall. Add the following rule to /etc/pf.conf if it is not already present:

pass in on egress proto tcp to port { 80, 443 }

Reload the firewall with pfctl -f /etc/pf.conf to apply the change. Verify Nginx is listening with sockstat -4l | grep nginx.

Choosing a Let's Encrypt Client

Several ACME clients work on FreeBSD. The most common options are security/py-certbot (Python-based, full-featured), security/acme.sh (shell script), and getssl (lightweight shell script with no dependencies beyond curl and openssl). For FreeBSD servers where simplicity matters, we recommend getssl. It is a single shell script with minimal overhead, no Python dependency chain, and straightforward configuration.

Install getssl from ports or fetch it directly from GitHub:

pkg install getssl

If getssl is not available in your package repository, you can fetch it manually:

fetch -o /usr/local/bin/getssl https://raw.githubusercontent.com/srvrco/getssl/master/getssl
chmod 700 /usr/local/bin/getssl

Setting Up getssl

Create the initial configuration for your domain. This generates a directory structure under ~/.getssl/ with a global config and a per-domain config file:

getssl -c yourdomain.com

Edit the domain configuration at ~/.getssl/yourdomain.com/getssl.cfg and set the following values:

CA="https://acme-v02.api.letsencrypt.org"
SANS="www.yourdomain.com"
ACL=('/var/www/letsencrypt/.well-known/acme-challenge')
USE_SINGLE_ACL="true"
DOMAIN_CERT_LOCATION="/usr/local/etc/nginx/certs/yourdomain.com.pem"
DOMAIN_KEY_LOCATION="/usr/local/etc/nginx/certs/yourdomain.com.key"
CA_CERT_LOCATION="/usr/local/etc/nginx/certs/yourdomain.com.ca.pem"
RELOAD_CMD="service nginx reload"

Create the webroot challenge directory so Let's Encrypt can verify domain ownership:

mkdir -p /var/www/letsencrypt/.well-known/acme-challenge

Add a location block to your Nginx configuration to serve the ACME challenge files. This goes inside your existing server block for port 80:

location /.well-known/acme-challenge/ {
    root /var/www/letsencrypt;
}

Obtaining the Certificate

Run getssl to request and obtain your certificate from Let's Encrypt:

getssl yourdomain.com

On success, getssl downloads the signed certificate and places the files in the locations specified in your config. The certificate, private key, and CA bundle are copied to /usr/local/etc/nginx/certs/. Verify the files exist:

ls -la /usr/local/etc/nginx/certs/yourdomain.com.*

Nginx SSL Configuration on FreeBSD

On FreeBSD, the Nginx configuration lives at /usr/local/etc/nginx/ -- not /etc/nginx/ as on Linux distributions. Create or edit your site configuration at /usr/local/etc/nginx/conf/yourdomain.conf:

# HTTP to HTTPS redirect
server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;

    location /.well-known/acme-challenge/ {
        root /var/www/letsencrypt;
    }

    location / {
        return 301 https://yourdomain.com$request_uri;
    }
}

# HTTPS server
server {
    listen 443 ssl http2;
    server_name yourdomain.com www.yourdomain.com;

    ssl_certificate     /usr/local/etc/nginx/certs/yourdomain.com.pem;
    ssl_certificate_key /usr/local/etc/nginx/certs/yourdomain.com.key;

    root /home/webs/yourdomain.com/public;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

Test the configuration and reload Nginx:

nginx -t && service nginx reload

SSL/TLS Best Practices

A basic SSL setup works, but achieving an A+ on SSL Labs requires tuning the protocol and cipher configuration. Add these directives to your server block or to a shared include file at /usr/local/etc/nginx/conf/ssl-params.conf:

ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;

ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;

ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /usr/local/etc/nginx/certs/yourdomain.com.ca.pem;
resolver 1.1.1.1 8.8.8.8 valid=300s;
resolver_timeout 5s;

This configuration disables older protocols (TLS 1.0 and 1.1), enables OCSP stapling to speed up certificate validation, and configures a session cache for performance. The cipher list prioritizes AEAD ciphers with forward secrecy.

Security Headers

SSL/TLS encrypts the connection, but security headers protect against common web attacks. Add these headers to your HTTPS server block:

add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

The HSTS header tells browsers to always use HTTPS for your domain. The max-age value of 63072000 seconds equals two years. Once HSTS is active, browsers will refuse to connect over plain HTTP even if a user types http:// in the address bar.

Automated Renewal

Let's Encrypt certificates expire after 90 days. Automated renewal is essential. On FreeBSD, you can use cron or the native periodic framework. The simplest approach is a weekly cron job:

0 3 * * 6 /usr/local/bin/getssl -a && service nginx reload

This runs every Saturday at 3 AM, checks all configured domains for certificates nearing expiration, renews them if needed, and reloads Nginx to pick up the new certificates. For FreeBSD-native scheduling, you can add a script to /usr/local/etc/periodic/weekly/ instead. Test the renewal process manually before relying on automation:

getssl -a

Monitor the output to confirm that certificate checking and renewal work correctly. If you run multiple domains on the same server, getssl handles all of them with the -a flag by iterating through each domain directory under ~/.getssl/.

Testing Your Configuration

After completing the setup, verify everything works. Run the Qualys SSL Labs test at ssllabs.com/ssltest/ to get a detailed grade. From the FreeBSD command line, test the connection with openssl:

openssl s_client -connect yourdomain.com:443 -servername yourdomain.com

Check that your security headers are being served:

curl -I https://yourdomain.com

Verify that Nginx is listening on port 443:

sockstat -4l | grep 443

You should see Nginx bound to *:443. If the SSL Labs test returns anything below an A+, review your cipher configuration and ensure HSTS is active with the preload flag.

Learn about our security services

Need help with FreeBSD server security?

From SSL configuration to full security hardening with pf firewalls -- our engineers protect FreeBSD production infrastructure at scale.

Schedule a Consultation