Cover Image

Sette opp NGINX som reverse-proxy

 Thu 2018-01-11    Web

Denne artikkelen tar for seg oppsett av NGINX som reverse-proxy for en eller flere interne servere på et nettverk, der man bare har én offentlig IP-adresse tilgjengelig.

INNLEDNING

Jeg har en server stående i kjelleren stuen, og ønsket at denne (blant annet) skulle være ansvarlig for å servere websider for en del domener til verden utenfor.  For å få dette til på en best mulig måte var det en hel del forutsetninger som måtte løses, og en liste over hva jeg ønsket skulle være på plass ble utarbeidet. Den så til slutt slik ut:

  • Må funke med alle domenene jeg eier
  • Må funke med Apache-autoconfigen jeg kjører på webserveren bak proxyen
  • Må funke med SSL-sertifikater
  • Må la meg enkelt kunne spinne opp nye servere og legge dem til for å kunne nå dem fra utsiden

Etterhvert kom det noen andre punkter til på listen, mer om det senere.

NGINX

Jeg er glad i FreeBSD og det falt da naturlig å installere FreeBSD 11.1 og Nginx fra ports. Jeg satt opp en relativt standard config for Nginx, men fikk ingenting til å virke. Siden jeg hadde litt tidspress på meg installerte jeg Ubuntu Server 17.10, installerte siste versjon av Nginx fra Nginx' eget repo, og så fungerte det som forventet.

18.02.2018 ble certbot-nginx lagt til i FreeBSD ports, og jeg kunne da dumpe Linux-serveren til fordel for FreeBSD.

FreeBSD 11.1-RELEASE ble installert, oppdatert til -p9 via freebsd-update, ports ble oppdatert via portsnap, og et knippe essensielle programmer ble installert.

Til slutt hadde jeg et fungerende utgangspunkt for installasjon av www/nginx-devel, security/py-certbot og security/py-certbot-nginx.

Nginx ble installert med følgende options:

+--------------------------- nginx-devel-1.13.10 ------------------------------+
| +--------------------------------------------------------------------------+ |
| | [x] DSO Enable dynamic modules support                                   | |
| | [ ] DEBUG Build with debugging support                                   | |
| | [ ] DEBUGLOG Enable debug log (--with-debug)                             | |
| | [x] FILE_AIO Enable file aio                                             | |
| | [x] IPV6 Enable IPv6 support                                             | |
| | [ ] GOOGLE_PERFTOOLS Enable google perftools module                      | |
| | [x] HTTP Enable HTTP module                                              | |
| | [x] HTTP_ADDITION Enable http_addition module                            | |
| | [x] HTTP_AUTH_REQ Enable http_auth_request module                        | |
| | [x] HTTP_CACHE Enable http_cache module                                  | |
| | [x] HTTP_DAV Enable http_webdav module                                   | |
| | [x] HTTP_FLV Enable http_flv module                                      | |
| | [ ] HTTP_GEOIP Enable http_geoip module                                  | |
| | [x] HTTP_GZIP_STATIC Enable http_gzip_static module                      | |
| | [x] HTTP_GUNZIP_FILTER Enable http_gunzip_filter module                  | |
| | [ ] HTTP_IMAGE_FILTER Enable http_image_filter module                    | |
| | [x] HTTP_MP4 Enable http_mp4 module                                      | |
| | [ ] HTTP_PERL Enable http_perl module                                    | |
| | [x] HTTP_RANDOM_INDEX Enable http_random_index module                    | |
| | [x] HTTP_REALIP Enable http_realip module                                | |
| | [x] HTTP_REWRITE Enable http_rewrite module                              | |
| | [x] HTTP_SECURE_LINK Enable http_secure_link module                      | |
| | [x] HTTP_SLICE Enable http_slice module                                  | |
| | [x] HTTP_SSL Enable http_ssl module                                      | |
| | [x] HTTP_STATUS Enable http_stub_status module                           | |
| | [x] HTTP_SUB Enable http_sub module                                      | |
| | [ ] HTTP_XSLT Enable http_xslt module                                    | |
| | [x] MAIL Enable IMAP4/POP3/SMTP proxy module                             | |
| | [ ] MAIL_IMAP Enable IMAP4 proxy module                                  | |
| | [ ] MAIL_POP3 Enable POP3 proxy module                                   | |
| | [ ] MAIL_SMTP Enable SMTP proxy module                                   | |
| | [x] MAIL_SSL Enable mail_ssl module                                      | |
| | [x] HTTPV2 Enable HTTP/2 protocol support (SSL req.)                     | |
| | [ ] NJS Enable http_javascript module                                    | |
| | [x] STREAM Enable stream module                                          | |
| | [x] STREAM_SSL Enable stream_ssl module (SSL req.)                       | |
| | [x] STREAM_SSL_PREREAD Enable stream_ssl_preread module (SSL req.)       | |
| | [x] THREADS Enable threads support                                       | |
| | [ ] WWW Enable html sample files                                         | |
| | [ ] AJP 3rd party ajp module                                             | |
| | [ ] AWS_AUTH 3rd party aws auth module                                   | |
| | [ ] CACHE_PURGE 3rd party cache_purge module                             | |
| | [ ] CLOJURE 3rd party clojure module                                     | |
| | [ ] CT 3rd party cert_transparency module (SSL req)                      | |
| | [ ] ECHO 3rd party echo module                                           | |
| | [ ] FASTDFS 3rd party fastdfs module                                     | |
| | [ ] HEADERS_MORE 3rd party headers_more module                           | |
| | [ ] HTTP_ACCEPT_LANGUAGE 3rd party accept_language module                | |
| | [ ] HTTP_AUTH_DIGEST 3rd party http_authdigest module                    | |
| | [ ] HTTP_AUTH_KRB5 3rd party http_auth_gss module                        | |
| | [ ] HTTP_AUTH_LDAP 3rd party http_auth_ldap module                       | |
| | [ ] HTTP_AUTH_PAM 3rd party http_auth_pam module                         | |
| | [ ] HTTP_DAV_EXT 3rd party webdav_ext module                             | |
| | [ ] HTTP_EVAL 3rd party eval module                                      | |
| | [ ] HTTP_FANCYINDEX 3rd party http_fancyindex module                     | |
| | [ ] HTTP_FOOTER 3rd party http_footer module                             | |
| | [ ] HTTP_GEOIP2 3rd party geoip2 module                                  | |
| | [ ] HTTP_JSON_STATUS 3rd party http_json_status module                   | |
| | [ ] HTTP_MOGILEFS 3rd party mogilefs module                              | |
| | [ ] HTTP_MP4_H264 3rd party mp4/h264 module                              | |
| | [ ] HTTP_NOTICE 3rd party notice module                      | |
| | [ ] HTTP_PUSH 3rd party push module                      | |
| | [ ] HTTP_PUSH_STREAM 3rd party push stream module                | |
| | [ ] HTTP_REDIS 3rd party http_redis module                   | |
| | [ ] HTTP_RESPONSE 3rd party http_response module                 | |
| | [ ] HTTP_SUBS_FILTER 3rd party subs filter module                | |
| | [ ] HTTP_TARANTOOL 3rd party tarantool upstream module               | |
| | [ ] HTTP_UPLOAD 3rd party upload module                      | |
| | [ ] HTTP_UPLOAD_PROGRESS 3rd party uploadprogress module             | |
| | [ ] HTTP_UPSTREAM_CHECK 3rd party upstream check module              | |
| | [ ] HTTP_UPSTREAM_FAIR 3rd party upstream fair module            | |
| | [ ] HTTP_UPSTREAM_STICKY 3rd party upstream sticky module            | |
| | [ ] HTTP_VIDEO_THUMBEXTRACTOR 3rd party video_thumbextractor module      | |
| | [ ] HTTP_ZIP 3rd party http_zip module                       | |
| | [ ] ARRAYVAR 3rd party array_var module                      | |
| | [ ] BROTLI 3rd party brotli module                       | |
| | [ ] DRIZZLE 3rd party drizzlie module                    | |
| | [ ] DYNAMIC_UPSTREAM 3rd party dynamic_upstream module               | |
| | [ ] ENCRYPTSESSION 3rd party encrypted_session module            | |
| | [ ] FORMINPUT 3rd party form_input module                    | |
| | [ ] GRIDFS 3rd party gridfs module                       | |
| | [ ] ICONV 3rd party iconv module                         | |
| | [ ] LET 3rd party let module                         | |
| | [ ] LUA 3rd party lua module                         | |
| | [ ] MEMC 3rd party memc (memcached) module                   | |
| | [ ] MODSECURITY 3rd party mod_security module                | |
| | [ ] MODSECURITY3 3rd party mod_security v3 module                | |
| | [ ] NAXSI 3rd party naxsi module                         | |
| | [ ] PASSENGER 3rd party passenger module                     | |
| | [ ] POSTGRES 3rd party postgres module                       | |
| | [ ] RDS_CSV 3rd party rds_csv module                     | |
| | [ ] RDS_JSON 3rd party rds_json module                       | |
| | [ ] REDIS2 3rd party redis2 module                       | |
| | [ ] RTMP 3rd party rtmp module                           | |
| | [ ] SET_MISC 3rd party set_misc module                       | |
| | [ ] SFLOW 3rd party sflow module                         | |
| | [ ] SHIBBOLETH 3rd party shibboleth module                   | |
| | [ ] SLOWFS_CACHE 3rd party slowfs_cache module                   | |
| | [ ] SMALL_LIGHT 3rd party small_light module                 | |
| | [ ] SRCACHE 3rd party srcache module                     | |
| | [ ] VOD 3rd party vod module                         | |
| | [ ] VTS 3rd party vts module                         | |
| | [ ] X11 graphics/ImageMagick[-nox11] dependency                  | |
| | [ ] XSS 3rd party xss module                         | |
| +------------------------------------------------------------------100%----+ |
+------------------------------------------------------------------------------+
|                         < OK >              <Cancel>                         |
+------------------------------------------------------------------------------+

Oppsett av Nginx er i utgangspunktet ikke veldig komplisert, men det kan gjøres komplisert om man har avanserte behov. Heldigvis er ikke en reverse-proxy av det mest avanserte slaget, så configfilene man trenger er relativt korte. Under følger noen fungerende eksempler for bruk på FreeBSD. Andre systemer må endre filstier og evt brukeren Nginx skal kjøre som.

nginx.conf:

user www;
worker_processes 4;

error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
worker_connections 1024;
}

http {
include /usr/local/etc/nginx/mime.types;
default_type application/octet-stream;

log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

access_log /var/log/nginx/access.log main;

sendfile on;
#tcp_nopush on;

keepalive_timeout 65;

#gzip on;

# COMMON SSL DIRECTIVES

# CONNECTION CREDENTIALS
ssl_session_timeout 180m;
ssl_session_cache shared:SSL:20m;
ssl_session_tickets off;

# DISABLE SSL (only use TLS)
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;

# OPTIMIZED CIPHER SUITES
ssl_prefer_server_ciphers on;

# Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits, we're using 4096 bits
ssl_dhparam /usr/local/etc/nginx/ssl/dhparam4096.pem;

# STRICT TRANSPORT SECURITY (HSTS)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

# X-Frame-Options (for preventing your web site to be framed)
add_header X-Frame-Options "SAMEORIGIN" always;

# X-XSS-Protection
add_header X-XSS-Protection "1; mode=block" always;

# X-Content-Type-Options (to prevent mime-type sniffing)
add_header X-Content-Type-Options "nosniff" always;

# Remove server version from headers
server_tokens off;

# Referrer-Policy for when moving from https to http or vice versa
add_header Referrer-Policy "no-referrer";

include /usr/local/etc/nginx/conf.d/*.conf;
}

Jeg har lagt inn en del SSL-relaterte innstillinger i hovedconfigen, da disse vil være gjeldende for alle domener som skal ha SSL.

Deretter var det å få laget til noen virtuelle hoster og sette opp videresending til riktig server basert på hvilket domenenavn som ble spurt etter. Jeg opprettet en egen fil for backend-servere:

00_servers.conf:

# Backend servers

# Plain HTTP for receiving HTTP requests; these are redirected to their
# SSL enabled counterparts in other config files.

upstream webserver {
 server 192.168.1.1:80; # webserver, http
}

# SSL enabled servers for receiving HTTPS requests; these are redirected to their
# SSL enabled backends

upstream sslwebserver {
 server 192.168.1.1:443;
}

SSL for domenene hostet på 192.168.1.1 håndteres av reverseproxyen, så for å slippe ekstra kompleksitet for backend går all kommunikasjon mellom reverseproxy og backend ukryptert. Dette kan være et problem hvis noen kommer seg inn på nettverket mitt her, men siden alt er låst ned så godt det lar seg gjøre anser jeg det som trygt enn så lenge. Som nevnt innledningsvis hadde jeg noe tidspress på meg for å få dette til å funke, så enkelte snarveier ble tatt der det ble ansett forsvarlig. Full kryptering av trafikk mellom reverseproxy og backend er dog på to-do-listen.

Hvis webserveren står på samme nettverk som reverse-proxyen og klienten, vil man få opp det første nettstedet Nginx vet om hvis man skriver inn en feil nettadresse. Dette er standard oppførsel fra Nginx, men kan til en viss grad unngås ved å spesifisere en default_server som håndterer alt Nginx ikke vet om. Det gjøres veldig enkelt på denne måten:

# Default server configuration
#
server {
    listen 80 default_server;
    listen [::]:80 default_server;

    root /var/www/html;

    index index.html index.htm;

    server_name _;

    location / {
            try_files $uri =404;
    }
}

I korte trekk:

listen 80 default_server betyr at dette er den oppføringen Nginx skal se på hvis hostnavnet det spørres etter ikke finnes.

location / {
    try_files $uri =404;
}

Denne location-blokken ser etter filen det spørres etter; hvis den ikke finnes returneres feilmelding 404 Not Found.

Veldig enkelt og veldig effektivt.

Neste steg var å sette opp virtualhost for et faktisk domene.

For domener uten SSL ble det seende slik ut:

http.conf:

server {
listen 80;
  server_name example.com *.example.com example.net *.example.net;
  root /usr/local/www;
  index index.html index.htm;

## send request to backend ##
  location / {
    proxy_pass http://webserver;
    proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
    proxy_redirect off;
    proxy_buffering off;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  }
}

Det dette i korte trekk gjør, er å se på hvilket domenenavn som etterspørres, og sende forespørselen videre til backend-serveren. Den siste proxy_set_header-linjen er med for å også videresende IP-adressen fra klienten som etterspør data; dette for å kunne se riktig IP-adresse i loggene på backend-serveren, og ikke bare IP-adressen til reverseproxyen.

For domener med SSL, ser en virtualhost slik ut:

example.com.conf:

server {
 listen 80;
 server_name example.com www.example.com;
 rewrite ^(.*) https://$server_name$1 permanent;
}

server {
 listen 443 ssl http2;
 server_name example.com www.example.com;

 root /usr/local/www;

 access_log /var/log/nginx/vhosts/example.com.access_log;
 error_log /var/log/nginx/vhosts/example.com.error_log error;

ssl on;
 ssl_certificate /usr/local/etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
 ssl_certificate_key /usr/local/etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot
 include /usr/local/etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot

## send request back to back end ##
 location / {
 proxy_pass https://webserver;
 proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
 proxy_redirect off;
 proxy_buffering off;
 proxy_set_header Host $host;
 proxy_set_header X-Real-IP $remote_addr;
 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 proxy_set_header X-Forwarded-Proto https;
 }
}

Den første server-blokken ser på forespurt domenenavn, matcher det mot server_name (som kun bør inneholde det/de domenet med evt underdomener SSL-sertifikatet er utstedt for), og sender det til en SSL-oppsatt server-blokk med en permanent redirect. I nginx.conf hadde jeg også satt opp noe som heter HSTS for å instruere nettleseren på klienten til å alltid koble til domenet med HTTPS. Denne innstillingen er gyldig for alle domener som har SSL satt opp siden den er satt i en global kontekst.

Den neste server-blokken tar seg av selve oppsettet av virtualhosten. Det lyttes på port 443, http2 er aktivert, vi setter en fiktiv webroot (kan strengt tatt utelates fullstendig siden Nginx ikke server noen filer). Jeg spesifiserer også egne loggfiler for domenet for å enklere kunne filtrere ut eventuelle problemer.

SSL aktiveres og det spesifiseres stier til sertifikatene. Sertifikatene er fra Let's Encrypt og er gratis Domain Validation-domener, dvs de indikerer bare at domenet har SSL aktivert og at sertifikatet er gyldig for det spesifiserte domenet. Det utføres ingen som helst form for autentisering eller verifisering av faktisk eierskap eller organisasjon bak domenet, kun at den som ber om sertifikatet har kontroll over domenet.

Etter sertifikatbiten kommer selve proxydelen som videresender forespørselen til backend. Kommunikasjonen her foregår som nevnt ukryptert, men alle data proxyen får i retur fra backend sendes kryptert mellom proxy og spørrende klient.

Jeg har et domene som har et SSL-sertifikat fra PositiveSSL/Comodo; oppsettet av dette er noe annerledes da det er noen flere steg å ta før man er i mål. Når man kjøper et SSL-sertifikat fra PositiveSSL får man en hel haug filer tilbake:

$ ls
AddTrustExternalCARoot.crt
COMODORSAAddTrustCA.crt
COMODORSADomainValidationSecureServerCA.crt
PositiveSSLCA2.crt
www.example.com.crt
www.example.com.csr
www.example.com.key
www.example.com.pfx

www.example.com.csr er sertifikatforespørselsfilen som ble sendt til PositiveSSL, resten er det jeg fikk tilbake i en zip-fil. For å sikre korrekt funksjon med Nginx må følgende gjøres:

cat www.example.com.crt COMODORSADomainValidationSecureServerCA.crt COMODORSAAddTrustCA.crt > example.com-ssl-bundle.crt

Man spesifiserer så example.com-ssl-bundle.crt som sertifikat på linjen ssl_certificate i oppsettet over.

CERTBOT og CERTBOT-NGINX

security/py-certbot hadde ingen options.

security/py-certbot-nginx ble installert med følgende options:

+------------------------ py27-certbot-nginx-0.22.2 ---------------------------+
| +--------------------------------------------------------------------------+ |
| | [x] DOCS Build and/or install documentation                              | |
| +--------------------------------------------------------------------------+ |
+------------------------------------------------------------------------------+
|                         < OK >              <Cancel>                         |
+------------------------------------------------------------------------------+

Siden jeg migrerte fra Linux til FreeBSD ble ikke certbot kjørt for å opprette konto og spørre om sertifikat, men dette er det som skjer når det blir gjort:

En kommentar til kommandolinjen:

  • --nginx forteller Certbot at vi kjører Nginx og at nginx-pluginen skal brukes
  • -d example.com er domenet vi vil ha SSL-sertifikat for. Man kan spesifisere -d example.com -d www.example.com evt -d example.com,www.example.com for å få et sertifikat gyldig for både example.com og subdomenet www.example.com.

Første gang man kjører certbot spør det etter litt informasjon; min input er med fet skrift (dette er kjørt på Ubuntu, derfor er filstiene ikke riktig for FreeBSD):

# certbot --nginx -d example.com
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Enter email address (used for urgent renewal and security notices) (Enter 'c' to
cancel): hostmaster@example.com

-------------------------------------------------------------------------------
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 at
https://acme-v01.api.letsencrypt.org/directory
-------------------------------------------------------------------------------
(A)gree/(C)ancel: A

-------------------------------------------------------------------------------
Would you be willing 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 EFF and
our work to encrypt the web, protect its users and defend digital rights.
-------------------------------------------------------------------------------
(Y)es/(N)o: N
Obtaining a new certificate
Performing the following challenges:
http-01 challenge for example.com
http-01 challenge for www.example.com
Waiting for verification...
Cleaning up challenges
Deployed Certificate to VirtualHost /etc/nginx/conf.d/http.conf for www.example.com, example.com
Deployed Certificate to VirtualHost /etc/nginx/conf.d/http.conf for www.example.com, example.com

Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.
-------------------------------------------------------------------------------
1: No redirect - Make no further changes to the webserver configuration.
2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for
new sites, or if you're confident your site works on HTTPS. You can undo this
change by editing your web server's configuration.
-------------------------------------------------------------------------------
Select the appropriate number [1-2] then [enter] (press 'c' to cancel): 1

-------------------------------------------------------------------------------
Congratulations! You have successfully enabled https://example.com and
https://www.example.com

You should test your configuration at:
https://www.ssllabs.com/ssltest/analyze.html?d=example.com
https://www.ssllabs.com/ssltest/analyze.html?d=www.example.com
-------------------------------------------------------------------------------

IMPORTANT NOTES:
- Congratulations! Your certificate and chain have been saved at:
/etc/letsencrypt/live/example.com/fullchain.pem
Your key file has been saved at:
/etc/letsencrypt/live/example.com/privkey.pem
Your cert will expire on 2018-04-18. To obtain a new or tweaked
version of this certificate in the future, simply run certbot again
with the "certonly" option. To non-interactively renew *all* of
your certificates, run "certbot renew"
- If you like Certbot, please consider supporting our work by:

Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
Donating to EFF: https://eff.org/donate-le

IMPORTANT NOTES:
 - Your account credentials have been saved in your Certbot
 configuration directory at /etc/letsencrypt. You should make a
 secure backup of this folder now. This configuration directory will
 also contain certificates and private keys obtained by Certbot so
 making regular backups of this folder is ideal.

#

Merk at hvis man har en komplett virtualhost-fil i Nginx (slik jeg hadde), så må man kommentere ut SSL-biten før man kjører certbot for å få et sertifikat. Grunnen til dette er at Nginx ikke vil starte hvis sertifikatfilene ikke finnes, og de finnes ikke før man kjører Certbot. Videre må Nginx kjøre når man kjører Certbot, ellers vil ikke Certbot finne riktig virtualhost å legge sertifikatlinjene inn i. Litt Catch22 der, altså, men heldigvis lett å komme seg rundt.

NB: Let's Encrypt har deaktivert http-01-challenges og v01-APIet for utstedelse av sertifikater siden denne artikkelen ble skrevet; anbefalt challenge-metode pr mars 2019 er dns-01 og v02 av APIet. Denne versjonen støtter også wildcard-sertifikater for *.example.com, slik at man ikke trenger få utstedt enkeltsertifikater for www.example.com, foo.example.com, bar.example.com, osv.

Etter installasjon ble hele /etc/letsencrypt på Ubuntu pakket i en tar.gz-fil via tar -cpvzf /tmp/le.tgz /etc/letsencrypt, for å preservere filrettigheter og symlinks. /etc/nginx ble pakket ned på tilsvarende måte.

Før le.tgz ble pakket ut på FreeBSD, kjørte jeg certbot én gang uten noen options for å opprette mappestrukturen under /usr/local/etc/letsencrypt. Da denne var på plass ble le.tgz pakket ut hit. Alle stier i alle conf-filer under renewal må endres fra /etc/letsencrypt til /usr/local/etc/letsencrypt før certbot kjøres igjen, ellers vil kjedelige ting skje. Serveren må også ha samme navn og IP-adresse som den den erstatter, så ikke kjør noe som helst før dette er gjort.

nginx.tgz ble pakket ut til /usr/local/etc/nginx. Stier i alle configfiler ble endret til /usr/local/etc/letsencrypt (for sertifikater) og ellers tilpasset bruk på FreeBSD. nginx.conf på Ubuntu angir "nginx" som brukeren nginx skal kjøre under; denne finnes ikke på FreeBSD, som defaulter til å bruke www for dette formålet. Configen ble endret til å bruke www i stedet. Da dette var gjort fikk serveren satt nytt hostname og IP-adresse i rc.conf, og den gamle serveren ble slått av mens den nye ble startet på nytt. Etter omstart ble Nginx startet manuelt for å se at alt virket (det gjorde det), og certbot kjørt en gang uten noen parametre for å se om det virket (det gjorde det ikke).

Feilmeldingen fra certbot var som følger:

# certbot
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Error while running nginx -c /etc/nginx/nginx.conf -t.

nginx: [emerg] open() "/etc/nginx/nginx.conf" failed (2: No such file or directory)
nginx: configuration file /etc/nginx/nginx.conf test failed

Certbot doesn't know how to automatically configure the web server on
this system. However, it can still get a certificate for you. Please run
"certbot certonly" to do so. You'll need to manually configure your web
server to use the resulting certificate.

Innholdet i /var/log/letsencrypt/letsencrypt.log:

# cat /var/log/letsencrypt/letsencrypt.log
2018-04-04 18:52:42,032:DEBUG:certbot.main:certbot version: 0.22.2
2018-04-04 18:52:42,033:DEBUG:certbot.main:Arguments: []
2018-04-04 18:52:42,033:DEBUG:certbot.main:Discovered plugins: PluginsRegistry(PluginEntryPoint#manual,PluginEntryPoint#nginx,PluginEntryPoint#null,PluginEntryPoint#standalone,PluginEntryPoint#webroot)
2018-04-04 18:52:42,051:DEBUG:certbot.log:Root logging level set at 20
2018-04-04 18:52:42,052:INFO:certbot.log:Saving debug log to /var/log/letsencrypt/letsencrypt.log
2018-04-04 18:52:42,052:DEBUG:certbot.plugins.selection:Requested authenticator None and installer None
2018-04-04 18:52:42,065:ERROR:certbot.util:Error while running nginx -c /etc/nginx/nginx.conf -t.

nginx: [emerg] open() "/etc/nginx/nginx.conf" failed (2: No such file or directory)
nginx: configuration file /etc/nginx/nginx.conf test failed

2018-04-04 18:52:42,065:DEBUG:certbot.plugins.disco:Misconfigured PluginEntryPoint#nginx: Error while running nginx -c /etc/nginx/nginx.conf -t.

nginx: [emerg] open() "/etc/nginx/nginx.conf" failed (2: No such file or directory)
nginx: configuration file /etc/nginx/nginx.conf test failed
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/site-packages/certbot/plugins/disco.py", line 126, in prepare
    self._initialized.prepare()
  File "/usr/local/lib/python2.7/site-packages/certbot_nginx/configurator.py", line 134, in prepare
    self.config_test()
  File "/usr/local/lib/python2.7/site-packages/certbot_nginx/configurator.py", line 798, in config_test
    raise errors.MisconfigurationError(str(err))
MisconfigurationError: Error while running nginx -c /etc/nginx/nginx.conf -t.

nginx: [emerg] open() "/etc/nginx/nginx.conf" failed (2: No such file or directory)
nginx: configuration file /etc/nginx/nginx.conf test failed

2018-04-04 18:52:42,066:DEBUG:certbot.plugins.selection:Single candidate plugin: * nginx
Description: Nginx Web Server plugin - Alpha
Interfaces: IAuthenticator, IInstaller, IPlugin
Entry point: nginx = certbot_nginx.configurator:NginxConfigurator
Initialized: <certbot_nginx.configurator.NginxConfigurator object at 0x80217c0d0>
Prep: Error while running nginx -c /etc/nginx/nginx.conf -t.

nginx: [emerg] open() "/etc/nginx/nginx.conf" failed (2: No such file or directory)
nginx: configuration file /etc/nginx/nginx.conf test failed

2018-04-04 18:52:42,067:DEBUG:certbot.plugins.selection:Selected authenticator None and installer None

Hvis jeg opprettet en symlink fra /usr/local/etc/nginx til /etc/nginx (ln -s /usr/local/etc/nginx /etc/nginx), så funket alt som det skulle. Jeg var usikker på om problemet skyldtes at jeg hadde kopiert over LE-filene fra Linux eller om dette var en innstilling i certbot som ble satt ved første kjøring, ved installasjon eller hva, så flere undersøkelser var nødvendig.

Etter mye søking gjennom systemet og etter å ha kjørt certbot -vvvvvv for å få mest mulig debugoutput, fant jeg omsider /usr/local/lib/python2.7/site-packages/certbot_nginx/constants.py, som setter nginx server_root, der certbot ser etter configfilene for nginx. Denne var satt til /etc/nginx, så jeg endret den til /usr/local/etc/nginx, rekompilerte .pyc- (python -m py_compile constants.py) og .pyo (python -O -m py_compile constants.py)-filene, fjernet symlinken fra /etc/nginx og startet nginx på nytt.

Tilsynelatende fungerte ting som de skulle, men fornyelse av et sertifikat feilet miserabelt og indikerte dermed at det var noe mer som ikke stemte. Jeg begynte å lete etter flere steder /etc/nginx var hardkodet, men klarte ikke finne noe.

Jeg var heller ikke sikker på om denne stien ble satt ved kompilering/installasjon eller om den ble plukket opp fra configfilene jeg kopierte fra Linuxboksen.

For å finne ut av dette spant jeg opp en ny virtuell server, installerte FreeBSD, nginx, py-certbot og py-certbot-nginx på den, og uten å gjøre noe som helst med den etter installasjonen, sjekket jeg constants.py. Joda, her var server_root satt til /etc/nginx, så den blir sannsynligvis ikke rørt av installasjonen i det hele tatt. Jeg har sendt inn en bugrapport for dette til port-maintainer, så forhåpentligvis blir dette fikset snart.

Løsningen så langt er hvertfall å installere alt som det er direkte fra ports, og deretter opprette symlink fra /usr/local/etc/nginx til /etc/nginx.

Når alt dette omsider var på plass og fungerte som forventet, var det på tide å starte skikkelig med Certbot.

SSL-sertifikater

Man kan gjøre det på gamlemåten og generere en CSR, sende denne til en SSL-sertifikatpusher, betale penger for et sertifikat og så installere det på vanlig måte. Det andre alternativet, om man kun trenger et Domain Validated-sertifikat og/eller ikke vil betale for det, er å bruke Let's Encrypt (LE). Det eneste problemet med LE-sertifikater er at de bare har 90 dagers gyldighet.

LE har laget et trivelig script for å automatisere prosessen. For å automatisere fornyelse av sertifikatene kan man legge inn en cronjob som kjører hver 12. time for å sjekke om det er noen sertifikater som trenger fornyelse, fornyer disse ved behov og starter Nginx på nytt automatisk for å lese inn nye sertifikater.

Dette er alt man trenger å legge inn i /etc/crontab for å automatisere fornyelsen av sertifikatene:

# certbot auto-renew
0       */12    *       *       *       root    test -x /usr/local/bin/certbot && /usr/local/bin/perl -e 'sleep int(rand(600))' && /usr/local/bin/certbot -q renew --post-hook "/usr/sbin/service nginx reload" >> /var/log/certbot-renew.log

Denne kommandoen sjekker om /usr/local/bin/certbot er kjørbar, kjører en perl-rutine for å sove et tilfeldig antall sekunder innen en ramme på 600 sekunder (10 minutter), og deretter kjører den fornyelse av sertifikatene. --post-hook "/usr/sbin/service nginx reload" kjøres kun dersom ett eller flere sertifikater faktisk ble fornyet, og reloader da configfilen til nginx for å laste inn de nye sertifikatene. Hele denne prosessen kjøres en gang hver 12. time, hver dag.

Hvis man har "vanlige" SSL-sertifikater må disse naturlig nok fornyes på vanlig måte via utstederen man fikk dem fra. I mitt tilfelle har jeg tenkt å la det løpe ut og så erstatte det med et sertifikat fra Let's Encrypt da disse sertifikatene gjør akkurat samme nytten.