Once again, when you try to combine an unpopular app on an unpopular platform, and you want the latest version of them, the journey is long. Today, we want a FreeNAS 11.3 jail hosting Wekan 4.01, the Trello-like kanban-style board app, behind nginx 1.18.0 with OpenSSL 1.1.1g using TLS 1.3. You may want to do that if you don’t want to share your private boards with yet another cloud company and its likely ambiguous privacy policy.
1. Create a new jail
Assuming you already have created jails in the past, your FreeNAS is ready to make new ones quickly.
Log in to your FreeNAS admin panel, go to Jails, click ADD.
Give it a name (here “wekan-test”), and select the latest release version available, then Next.

Check VNET and select either DHCP (if your router can be configured to give static DHCP lease for instance), or give it a static IP. Next. Submit.

Start the jail by clicking the START button.

Then, SSH to your FreeNAS instance, locate your Jail ID using jls
, then jexec <JID> csh
.

2. Install dependencies
Install MongoDB 4.0:
pkg install mongodb40 mongodb40-tools
sysrc mongod_enable=YES
service mongod start
Don’t worry about exposing your DB to the world: MongoDB no longer listens on 0.0.0.0 by default, it only creates a local socket as you can see with sockstat -L
:
# sockstat -L
USER COMMAND PID FD PROTO LOCAL ADDRESS FOREIGN ADDRESS
mongodb mongod 99827 10 stream /tmp/mongodb-27017.sock
Install node.js 12:
pkg install node12 npm-node12 bcrypt
Make sure python2 can be found by npm:
cd /usr/local/bin
ln -s python2.7 python2
Install some other tools
pkg install nano
3. Install Wekan
Create new user
adduser
Use csh
for the shell, and use an empty password. We will disable login after anyway.

Fetch sources
Go to https://releases.wekan.team/ and locate the ZIP or TAR package for the latest wekan release. This is a Meteor-wrapped bundle, easier to deploy, which is different than cloning the Github repo.

Right-click, copy link address.
Back in the jail, switch to the new user wekan, fetch and decompress the archive in the user’s home directory.
su wekan
cd /home/wekan
fetch https://releases.wekan.team/wekan-4.01.zip
tar xzpf wekan-4.01.zip
Remove phantomJS
The version needed is not available on FreeBSD but Wekan works without it.
cd ~/bundle/programs/server/npm/node_modules/meteor/lucasantoniassi_accounts-lockout/node_modules
rm -rf phantomjs-prebuilt
Run npm install a first time
cd ~/bundle/programs/server
npm install
This will fail with a bcrypt error and a node-pre-gyp error.

Fix the node-pre-gyp error
rm -rf /usr/home/wekan/bundle/programs/server/node_modules/.bin/node-pre-gyp
npm install node-pre-gyp
Fix the bcrypt error
npm install bcrypt
cd npm/node_modules
mv bcrypt ~/
cd ../..
npm install
mv ~/bcrypt npm/node_modules
npm install
should have completed without error this time.
Install fibers
npm install fibers
4. Configure Wekan
Make a config file
Next, we need to prepare a config file that will apply all the environment variables needed by Wekan.
Grab https://raw.githubusercontent.com/wekan/wekan/master/start-wekan.sh as /home/wekan/start-wekan.sh
.
cd ~
fetch https://raw.githubusercontent.com/wekan/wekan/master/start-wekan.sh
Open the file
nano start-wekan.sh
and comment the line cd .build/bundle
at the beginning, as well as the lines node main.js
and cd ../..
lines towards the end of the file:
#while true; do
#cd .build/bundle
[...]
#node main.js
# & >> ../../wekan.log
#cd ../..
#done
Next, adjust ROOT_URL to correspond to the URL you will be using Wekan with. For instance, you could configure an entry in your hosts file to map the FreeNAS jail’s IP with the name wekan (LAN use only). Through a DNS server on your network, you could make sure to resolve, let’s say wekan.lan to the jail’s IP. If you’re exposing Wekan to the internet, you probably will get a domain name for it.
This will give you something like this:
export ROOT_URL='https://my-super-wekan-setup.com'
For my example, I’ll do wekan-test.lan.
Note: this is not the IP/domain and port that Wekan will be listening on. This is the final form of the URL once served by nginx, which we will configure shortly.
Customize the local port that Wekan will be listening on, and make it bind to localhost only. This is achieved by setting the undocumented BIND_IP environment variable. You don’t want Wekan to be open to the world and directly reachable, it should go through nginx.
export PORT=3001
export BIND_IP=127.0.0.1
Make sure to also configure MAIL_URL, MAIL_FROM (not specified in the .sh file), WITH_API, and check other options as well.
Make it a service
Next, we want to start Wekan as a service and use the config we just made. Exit from su wekan
, then edit /usr/local/etc/rc.d/wekan
.
% exit
# nano /usr/local/etc/rc.d/wekan
Paste the content below into it:
#!/bin/sh
# PROVIDE: wekan
# REQUIRE: mongod nginx
# BEFORE:
# KEYWORD: shutdown
. /etc/rc.subr
name="wekan"
rcvar="wekan_enable"
pidfile="/var/run/${name}.pid"
. /home/wekan/start-wekan.sh
cd /home/wekan/bundle
command="/usr/sbin/daemon"
command_args="-P ${pidfile} -u wekan -r /usr/local/bin/node main.js"
load_rc_config $name
: ${wekan_enable:="NO"}
run_rc_command "$1"
Save and exit. Set the proper permissions:
chmod 555 /usr/local/etc/rc.d/wekan
Enable and start the service.
sysrc wekan_enable=yes
service wekan start
At this point, Wekan should be running, but is only accessible on localhost. One way to test if things are running well is to netcat to localhost on port 3001 (as configured in your start-wekan.sh) and send a simple HTTP request.
# nc localhost 3001
GET / HTTP/1.1
Host: wekan-test.lan
Accept: */*

Let’s now disable wekan login:
chsh -s /usr/sbin/nologin wekan
5. Install Nginx
Let’s assume you want the latest nginx version available, with support for TLS 1.3, and you don’t care about legacy clients. You can’t just pkg install nginx
. You will get an older version compiled against a version of OpenSSL that doesn’t even support TLS 1.3. You’d not be happy.
Fetch the latest OpenSSL source
Go to https://www.openssl.org/source/ and get the link to the .tar.gz file corresponding to the latest v1.1 release.

Today, this is https://www.openssl.org/source/openssl-1.1.1g.tar.gz.
Fetch the source:
cd /tmp
fetch https://www.openssl.org/source/openssl-1.1.1g.tar.gz
tar zxvf openssl-1.1.1g.tar.gz
Fetch the latest nginx source
Similarly, go to https://nginx.org/en/download.html and get the link to the .tar.gz file corresponding to the latest stable release.

Today, this is https://nginx.org/download/nginx-1.18.0.tar.gz.
Fetch the source.
cd /tmp
fetch https://nginx.org/download/nginx-1.18.0.tar.gz
tar zxvf nginx-1.18.0.tar.gz
Compile nginx with OpenSSL
Note: adjust the path to OpenSSL in the --with-openssl=
accordingly. Also, the list of modules for nginx is a small list but should be enough to run Wekan (probably even an overkill).
pkg install perl5
cd nginx-1.18.0
./configure --prefix=/usr/local/etc/nginx --with-cc-opt='-I /usr/local/include' --with-ld-opt='-L /usr/local/lib' --conf-path=/usr/local/etc/nginx/nginx.conf --sbin-path=/usr/local/sbin/nginx --pid-path=/var/run/nginx.pid --error-log-path=/var/log/nginx/error.log --user=www --group=www --modules-path=/usr/local/libexec/nginx --with-file-aio --http-client-body-temp-path=/var/tmp/nginx/client_body_temp --http-proxy-temp-path=/var/tmp/nginx/proxy_temp --http-log-path=/var/log/nginx/access.log --with-http_v2_module --with-http_addition_module --with-http_auth_request_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_realip_module --with-pcre --with-http_slice_module --with-http_ssl_module --with-openssl=../openssl-1.1.1g --with-http_stub_status_module --with-http_sub_module --with-threads
make
make install
Then test it:
# nginx -V
nginx version: nginx/1.18.0
built by clang 8.0.0 (tags/RELEASE_800/final 356365) (based on LLVM 8.0.0)
built with OpenSSL 1.1.1g 21 Apr 2020
TLS SNI support enabled
configure arguments: [...]
Configure nginx
Since we will overwrite the existing config, I find it easier to just delete nginx config file and recreate it:
rm /usr/local/etc/nginx/nginx.conf
touch /usr/local/etc/nginx/nginx.conf
chown root:wheel /usr/local/etc/nginx/nginx.conf
chmod 644 /usr/local/etc/nginx/nginx.conf
Edit this config file:
nano /usr/local/etc/nginx/nginx.conf
Paste the following content in the file:
user www;
events {
worker_connections 10;
# multi_accept on;
}
http {
# this section is needed to proxy web-socket connections
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
##
# Basic Settings
##
include mime.types;
default_type application/octet-stream;
client_max_body_size 100M;
server_tokens off;
charset utf-8;
sendfile on;
keepalive_timeout 60;
gzip on;
##
# Logging Settings
##
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
#if logging requests is needed
#access_log /var/log/access.log main;
access_log off;
error_log /var/log/nginx/error.log;
server {
listen 443 ssl http2;
server_name wekan-test.lan;
##
# SSL Settings
##
ssl_certificate /usr/local/etc/ssl/wekan.crt;
ssl_certificate_key /usr/local/etc/ssl/wekan.key;
#ssl_password_file /usr/local/etc/ssl/pass.txt;
ssl_protocols TLSv1.3;
#if clients can't connect (because they don't support TLSv1.3), use:
#ssl_protocols TLSv1.3 TLSv1.2;
#TLS 1.3 and FS TLS 1.2 ciphersuites with EC certificates only
ssl_ciphers "TLS_CHACHA20_POLY1305:TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256";
ssl_ecdh_curve X25519:secp521r1:secp384r1;
# if using a browser-trusted certificate
#ssl_stapling on;
#ssl_stapling_verify on;
ssl_session_timeout 1h;
ssl_session_cache shared:SSL:30m;
ssl_session_tickets off;
add_header Strict-Transport-Security "max-age=31536000;";
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
# Pass requests to Wekan.
# If you have Wekan at https://example.com/wekan , change location to:
# location /wekan {
location / {
# proxy_pass http://127.0.0.1:3001/wekan;
proxy_pass http://127.0.0.1:3001; # local Wekan instance
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade; # allow websockets
proxy_redirect 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;
# this setting allows the browser to cache the application in a way compatible with Meteor
# on every application update the name of CSS and JS file is different, so they can be cache infinitely (here: 30 days)
# the root path (/) MUST NOT be cached
#if ($uri != '/wekan') {
# expires 30d;
#}
}
}
}
At this point, you should customize server_name:
server_name my-super-wekan-setup.com
and your TLS certificate/private key (use EC certificates preferably, otherwise adapt ssl_ciphers):
ssl_certificate /usr/local/etc/ssl/wekan.crt;
ssl_certificate_key /usr/local/etc/ssl/wekan.key;
You can either use your own self-signed certificate or PKI, or get a browser-trusted certificate from Let’s Encrypt and automate the renewal using certbot. This is a separate exercise.
Make sure that the proxy_pass also reflects the port Wekan is listening on:
proxy_pass http://127.0.0.1:3001
Create a client_body_temp folder:
mkdir /var/tmp/nginx
chown www:www /var/tmp/nginx
Check the config:
nginx -t
Make nginx a service
This is needed since we did not install nginx with pkg install
. You can skip this step if you first pkg install nginx
, then overwrite the installation with make install
.
nano /usr/local/etc/rc.d/nginx
Copy the following:
#!/bin/sh
# $FreeBSD: branches/2020Q2/www/nginx/files/nginx.in 518572 2019-11-28 10:17:37Z joneum $
# PROVIDE: nginx
# REQUIRE: LOGIN cleanvar
# KEYWORD: shutdown
#
# Add the following lines to /etc/rc.conf to enable nginx:
# nginx_enable (bool): Set to "NO" by default.
# Set it to "YES" to enable nginx
# nginx_profiles (str): Set to "" by default.
# Define your profiles here.
# nginx_pid_prefix (str): Set to "" by default.
# When using profiles manually assign value to "nginx_"
# for prevent collision with other PIDs names.
# nginxlimits_enable (bool): Set to "NO" by default.
# Set it to yes to run `limits $limits_args`
# just before nginx starts.
# nginx_flags (str): Set to "" by default.
# Extra flags passed to start command.
# nginxlimits_args (str): Default to "-e -U www"
# Arguments of pre-start limits run.
# nginx_http_accept_enable (bool): Set to "NO" by default.
# Set to yes to check for accf_http kernel module
# on start-up and load if not loaded.
. /etc/rc.subr
name="nginx"
rcvar=nginx_enable
start_precmd="nginx_precmd"
restart_precmd="nginx_checkconfig"
reload_precmd="nginx_checkconfig"
configtest_cmd="nginx_checkconfig"
gracefulstop_cmd="nginx_gracefulstop"
upgrade_precmd="nginx_checkconfig"
upgrade_cmd="nginx_upgrade"
command="/usr/local/sbin/nginx"
_pidprefix="/var/run"
pidfile="${_pidprefix}/${name}.pid"
_tmpprefix="/var/tmp/nginx"
required_files=/usr/local/etc/nginx/nginx.conf
extra_commands="reload configtest upgrade gracefulstop"
[ -z "$nginx_enable" ] && nginx_enable="NO"
[ -z "$nginxlimits_enable" ] && nginxlimits_enable="NO"
[ -z "$nginxlimits_args" ] && nginxlimits_args="-e -U www"
[ -z "$nginx_http_accept_enable" ] && nginx_http_accept_enable="NO"
load_rc_config $name
if [ -n "$2" ]; then
profile="$2"
if [ "x${nginx_profiles}" != "x" ]; then
pidfile="${_pidprefix}/${nginx_pid_prefix}${profile}.pid"
eval nginx_configfile="\${nginx_${profile}_configfile:-}"
if [ "x${nginx_configfile}" = "x" ]; then
echo "You must define a configuration file (nginx_${profile}_configfile)"
exit 1
fi
required_files="${nginx_configfile}"
eval nginx_enable="\${nginx_${profile}_enable:-${nginx_enable}}"
eval nginx_flags="\${nginx_${profile}_flags:-${nginx_flags}}"
eval nginxlimits_enable="\${nginxlimits_${profile}_enable:-${nginxlimits_enable}}"
eval nginxlimits_args="\${nginxlimits_${profile}_args:-${nginxlimits_args}}"
nginx_flags="-c ${nginx_configfile} -g \"pid ${pidfile};\" ${nginx_flags}"
else
echo "$0: extra argument ignored"
fi
else
if [ "x${nginx_profiles}" != "x" -a "x$1" != "x" ]; then
for profile in ${nginx_profiles}; do
echo "===> nginx profile: ${profile}"
/usr/local/etc/rc.d/nginx $1 ${profile}
retcode="$?"
if [ "0${retcode}" -ne 0 ]; then
failed="${profile} (${retcode}) ${failed:-}"
else
success="${profile} ${success:-}"
fi
done
exit 0
fi
fi
# tmpfs(5)
nginx_checktmpdir()
{
if [ ! -d ${_tmpprefix} ] ; then
install -d -o www -g www -m 755 ${_tmpprefix}
fi
}
nginx_checkconfig()
{
nginx_checktmpdir
echo "Performing sanity check on nginx configuration:"
eval ${command} ${nginx_flags} -t
}
nginx_gracefulstop()
{
echo "Performing a graceful stop:"
sig_stop="QUIT"
run_rc_command ${rc_prefix}stop $rc_extra_args || return 1
}
nginx_upgrade()
{
echo "Upgrading nginx binary:"
reload_precmd=""
sig_reload="USR2"
run_rc_command ${rc_prefix}reload $rc_extra_args || return 1
sleep 1
echo "Stopping old binary:"
sig_reload="QUIT"
pidfile="$pidfile.oldbin"
run_rc_command ${rc_prefix}reload $rc_extra_args || return 1
}
nginx_precmd()
{
if checkyesno nginx_http_accept_enable
then
required_modules="$required_modules accf_http accf_data"
fi
nginx_checkconfig
if checkyesno nginxlimits_enable
then
eval `/usr/bin/limits ${nginxlimits_args}` 2>/dev/null
else
return 0
fi
}
run_rc_command "$1"
Give it the right permissions:
chmod 555 /usr/local/etc/rc.d/nginx
Enable the service and start it!
sysrc nginx_enable=yes
service nginx start
Access your Wekan
Now visit your Wekan’s URL.



Ultimate test: restart your jail to see if Wekan come back alive automatically.
Post-scriptum notes
Keep wekan, nginx and openssl updated. Unfortunately, the way we installed the latest versions will prevent us from using a simple pkg upgrade
to keep everything up-to-date 😦
Sources
https://www.gitmemory.com/issue/wekan/wekan/2662/538795856
https://github.com/wekan/wekan/wiki/Meteor-bundle
https://github.com/wekan/bundle/blob/master/programs/server/packages/webapp.js#L1196
https://github.com/wekan/wekan/issues/2662
https://github.com/wekan/wekan/wiki/Nginx-Webserver-Config