Server side SWIFT: Vapor, MySQL, and NGINX with SSL-cert running on Ubuntu instance from DigitalOcean
This is an effort to bring together many of the useful resources that helped me get my Swift Vapor server up and running on a machine instance from DigitalOcean with https and MySQL using the NGINX webserver.
I’m going to set this up with 2 domains (
beta.yourdomain.com). One being the primary
production App and the other serving a
beta version so that you can test your development on a live site, setup exactly as your
If you don’t want to set up both repositories you can ignore the stuff about
beta but you’ll still have to conform to the
production name structure for file paths in order for the provided scripts to work.
I rely heavily on Robert Bojor’s guide for setting up Perfect 2.0, Swift, Ubuntu and Digital Ocean. Other sources I attempt to link to where appropriate.
Also, before you invest the time required with the process I outline here you may want to check out Flock. It may be a much better and more well supported tool for your needs. Though I’m not sure if it deals with all the services I try to address here.
Versions of Packages used in this tutorial
- Swift : 3.1
- MySQL : 14.14
- Vapor : 1.5
- Nginx : 1.10.0
- Ubuntu: 16.04.2 x64
I believe I have fully tested the process after my edits, but it’s possible I missed something. There are also a couple areas I’d like to expand upon. I suspect people will have suggestions and possibly find bugs in my documentation. I will make every effort to incorporate public response.
Create Server instance on DigitalOcean
DigitalOcean is a great cloud computing platform for spinning up an instance of your internet accessible application. If you don’t already have an account you can signup here.
- Log in to your DigitalOcean account and, from the
Dropletsmenu tab select
- Distribution: select
Ubuntu 16.04.2 x64
- Size: Probably select the smallest one (
512 MB/1 CPU — 20 GB SSD disk — 1000 GB transfer). You’ll be able to change this later after you’re up and running.
- We don’t need
Block Storagefor a basic setup.
- Region: Select an option close to you.
- No additional options.
- SSH Key: Select an SSH key you’ve previously set up on DigitalOcean. If you haven’t done that yet, go ahead and do that now. You can use their tutorial for reference. This may not be entirely required, but this tutorial assumes you have that setup.
- You’ll create
1 Dropletand you can use the default hostname it provides you.
- Click on
This should create something like what you see below…
We’ll need to do this for the last step of getting SSL/HTTPS to work. Hopefully if you start the process now the DNS will refresh by the time you need to reference your domain name.
If you do not need or want to use SSL/HTTPS then you can skip this and always use the IP Address provided by DigitalOcean wherever I reference
In order for your domain name to correctly point to this new IP Address. You first need to visit your domain name registrar and change the Name Servers to reference:
Remember to add an
A record for both
beta which are directed to your
ubuntu droplet you just setup.
Once this is done you may need to request your domain registrar to
refresh DNS. It may take some time for the changes to take effect.
Configure the remote server
If your DNS has refreshed and you can
ping yourdomain.com then you should be able to ssh into your DigitalOcean remote server. I have stored my DigitalOcean RSA key in the file:
~/.ssh/id_rsa_digitalOcean you may have it saved as something different.
ssh -i ~/.ssh/id_rsa_digitalOcean root@YOUR_DOMAIN.COM -v
If the DNS has not yet refreshed you can still proceed by using the IP Address provided by DigitalOcean for your Droplet. Change
YOUR_DROPLET_IP to use the one for your own instance.
ssh -i ~/.ssh/id_rsa_digitalOcean root@
The first time you login you will get a prompt questioning the authenticity of the host. This is okay, answer yes when it asks you if you are sure you want to continue connecting.
We’re going to start by creating a new user on your remote server and setting that user up with
sudo privileges. This will allow you to run the
NGINX server from this user’s home directory and bind the service to the correct port mappings.
USER_NAME to the name you want to use for administering your remote server.
useradd -d /home/USER_NAME -m -s /bin/bash USER_NAME
echo "USER_NAME ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
Next, we’ll set this User up to use the same ssh keys that were set up when the droplet was created.
cp /root/.ssh/authorized_keys /home/USER_NAME/.ssh
chown -R USER_NAME.USER_NAME /home/USER_NAME/.ssh
Now logout from your remote server and try to log back in as the new user you just created. Adjusting
ssh -i LOCAL_KEY_PATH USER_NAME@YOUR_DROPLET_IP -v
I’m going to suggest adding this to the User’s
~/.bash_profile because I find it helpful. It’s not necessary though.
~/.bash_profile (I use
vi but feel free to use whichever editor you’re comfortable with.
Now source that file so the changes take effect.
Update packages installed by DigitalOcean
sudo dpkg-reconfigure locales
That will bring up a configuration page. You should be able to hit return through these next two pages…
Now update and upgrade…
sudo apt-get update
sudo apt-get upgrade -y
Next, install some of the packages required by Swift in order to compile, along with some other helpful utilities.
sudo apt-get install make clang libicu-dev pkg-config libssl-dev libsasl2-dev libcurl4-openssl-dev uuid-dev git curl wget unzip -y
Next we need to install Swift. First check
https://swift.org/download to make sure you know which is the latest version.
sudo wget https://swift.org/builds/swift-3.1-release/ubuntu1604/swift-3.1-RELEASE/swift-3.1-RELEASE-ubuntu16.04.tar.gz
sudo gunzip < swift-3.1-RELEASE-ubuntu16.04.tar.gz | sudo tar -C / -xv --strip-components 1
Let’s make sure that worked…
That should return something like:
Swift version 3.1 (swift-3.1-RELEASE)
If Swift has successfully installed then you can remove the tar.gz file:
sudo rm -f swift-3.1-RELEASE-ubuntu16.04.tar.gz
And before we get too far let’s just change back to our $HOME folder.
I’ve had problems compiling
release builds on DigtalOcean and adding a swap partition seems to have solved that issue.
DigitalOcean provides reference for this.
Allocate 4 Gigabytes for Swap
sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
Should return something like:
Setting up swapspace version 1, size = 4194300 KiB
no label, UUID=e2f1e9cf-c0a9-4ed4-b8ab-714b8a7d6944
Enable the Swap partition
sudo swapon /swapfile
Check to make sure this worked:
sudo swapon -s
You should get something like:
Filename Type Size Used Priority
/swapfile file 4194300 0 -1
Now Make the Swap partition permanent
sudo vi /etc/fstab
At the bottom of the file, you need to add a line that will tell the operating system to automatically use the file you created:
/swapfile none swap sw 0 0
This How-To will set up MySQL to host the database on the same server as our app. If you are going to use a database provider as a service you can omit
mysql-server from the following line.
sudo apt-get install mysql-client libmysqlclient-dev mysql-server -y
That should bring up a prompt to create a password for the root MySQL user. Create and remember this password.
mysqlclient.pc located in
You’ll remove the flags:
-fno-omit-frame-pointer so that the final file looks like:
Now let’s secure the MySQL server installation…
This will take you through a few prompts…
- enter root password. [Enter the password you recently created from the previous install step]
- Validate Password Plugin? This is probably a good idea. but not needed for this tutorial. [y]
- Change Root Password? We just created the root password so we probably don’t need to change it. [n]
- Remove Anonymous Users? [y]
- Disallow root login remotely? [y]
- Remove test database and access to it? [y]
- Reload privilege tables now? [y]
Now you should be able to check your setup.
That should give you something like:
mysql Ver 14.14 Distrib 5.7.17, for Linux (x86_64) using EditLine wrapper
Next, lets add a new User to control MySQL so you don’t have to login as the root user. You can reference DigitalOcean’s tutorial on this if you like.
mysql -u root -p
Enter the password when prompted. That should bring up the MySQL control shell with a
mysql> prompt. Make sure to change
USER_PASSWORD to suit your needs.
CREATE USER 'NEW_USER'@'localhost' IDENTIFIED BY 'USER_PASSWORD';
Now we need to grant that user with permissions to work with the databases. I’m going to grant that user with total access. Ultimately it may be best to change these for security reasons. I’m not even remotely an expert with security.
GRANT ALL PRIVILEGES ON * . * TO 'NEW_USER'@'localhost';
Now reload the privileges.
ctrl-d to exit the MySQL shell.
CONFIGURE FOR VAPOR
Now we’re going to get the tools needed to insure your ubuntu server is up and running with Vapor. You can reference the Vapor Docs.
You should be able to verify that everything Vapor needs up to this point is configured correctly with:
curl -sL check.vapor.sh | bash
That should return:
Now we can install the Vapor Toolbox. I don’t think this is a required step for the server side, but I’m doing it just in case.
eval "$(curl -sL https://apt.vapor.sh)"
sudo apt-get install vapor
You can verify it worked with:
It should return:
Usage: vapor <new|build|run|fetch|clean|test|xcode|version|self|heroku>
PREPARE USER’s HOME DIRECTORIES and GIT REPOSITORIES
You may want to customize some of these steps and locations to suit your own workflow. My process may not be considered “best practice”, and if you have suggestions on a better workflow I would very much like to incorporate it into this document.
I am going to set up directories which will be used to push our project’s git repositories, and some which will be used as our web-root for the NGINX server.
First the location for the
git repositories; One for the
beta repository and one for the
Insure that you are in your User’s home directory. (
cd ~ or just
APP_NAME should be the name you’d like to use as your web or api app. In my example I’ll just call mine “myWebApp” but this can be whatever you want.
We create an empty
Public repository now because eventually that’s what Vapor will look for as the public webroot, but we also need it for the step of creating the SSL certificates.
mkdir -p src/APP_NAME/beta/.git src/APP_NAME/production/.git appServer/APP_NAME/production/bin appServer/APP_NAME/production/Public appServer/APP_NAME/beta/bin appServer/APP_NAME/beta/Public
git init ~/src/APP_NAME/beta/.git/. --bare
git init ~/src/APP_NAME/production/.git/. --bare
We’re going to create a
post-receive hook for the git repositories. We don’t need the
.sample hooks, so I’ll remove them but feel free to keep them if you want.
First we’ll set up the
vi or your prefered editor create a
post-receive file which contains everything to build and move it over to the path NGINX will look for when serving the app. You should not need to change the values in this file unless you have changed the paths from what I’ve mentioned above.
post-receive hook executable.
chmod +x post-receive
Next let’s copy that
post-receive hook over to the
cp ~/src/APP_NAME/beta/.git/hooks/post-receive ~/src/APP_NAME/production/.git/hooks/post-receive
supervisor is a process that will run on the remote server and insure that your Swift App stays up and running.
sudo apt-get install supervisor -y
service supervisor restart
Now we need to setup the init scripts for your beta and production apps. Since we’re setting up for both a beta and production environment we’ll have a script for each of these.
Create a new file (you’ll need to use sudo) called
APP_NAME.conf and put the following lines in…
Now lets create another init script for the beta app. We’ll call this
Next, make supervisor aware of these new init scripts
sudo supervisorctl reread
On the remote DigtalOcean server instance… If everything has been set up correctly, you should be able to login by typing…
sudo apt-get install nginx -y
After it has installed we need to set up the virtual hosts for the
production sites. We’ll also remove the default nginx site.
sudo rm -rf default
Now, using your editor of choice (and sudo) create
APP_NAME.vhost which should contain…
Next, copy this file to
APP_NAME-beta.vhost and edit that; inside change the word
beta and, on ONLY lines 5 and 6 (the logs), where it says
APP_NAME change that to
APP_NAME-beta. I would have thought that we would need to change line 26 for the
beta vhost to point at
http://127.0.1:8181 because that’s the port the beta app is bound to, but it seems not to make a difference.
Let nginx know that these virtual hosts should be enabled…
sudo ln -s
sudo ln -s
Next, edit the
nginx.conf file located in
sudo mv nginx.conf nginx.conf.backup
Now (using sudo) create a new
Next, restart the nginx server…
sudo /etc/init.d/nginx restart
The https protocol is an extra layer of security for internet traffic. I won’t try to describe it any farther than that. It does become extra important if your webApp is serving as an API for iOS applications. It’s my understanding Apple has begun to enforce that calls made from your app will need to connect via
https and that regular old
http will no longer pass App Store requirements.
I’ll be referencing instructions for Securing Nginx with Let’s Encrypt from DigitalOcean.
Let’s go back to the User’s home directory to start:
This will Clone the Let’s Encrypt repository into
sudo git clone https://github.com/letsencrypt/letsencrypt /opt/letsencrypt
We’re going to request a certificate for both the
production sites. We’ll start with the
YOUR_DOMAIN.COM should be whatever you called it way back in the beginning when you were registering the
A records with DigitalOcean.
./letsencrypt-auto certonly -a webroot --webroot-path=/home/USER_NAME/appServer/APP_NAME/production/Public -d YOUR_DOMAIN.COM
Note: The Let’s Encrypt software requires superuser privileges (sudo).
letsencrypt initializes you will be prompted for some information.
When prompted, enter an email address to be used for notices and lost key recovery:
You must agree to the Subscriber Agreement…
If everything has worked then you should get a message:
- If you lose your account credentials, you can recover through
e-mails sent to USER_NAME@MAIL_DOMAIN.com
- Congratulations! Your certificate and chain have been saved at
cert will expire on 2016-03-15. To obtain a new version of the
certificate in the future, simply run Let's Encrypt again.
- Your account credentials have been saved in your Let's Encrypt
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 Let's
Encrypt so making regular backups of this folder is ideal.
- If like Let's Encrypt, please consider supporting our work by:
Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
Donating to EFF: https://eff.org/donate-le
Make note of the certificate path:
/etc/letsencrypt/live/BETA.YOUR_DOMAIN.COM/fullchain.pem and the expiration date.
Now repeat the process for the
./letsencrypt-auto certonly -a webroot --webroot-path=/home/USER_NAME/appServer/APP_NAME/beta/Public -d BETA.YOUR_DOMAIN.COM
Insure that the certificates exist; you can check by listing their location:
sudo ls -aF /etc/letsencrypt/live
That should return something like:
./ ../ BETA.YOUR_DOMAIN.COM/ YOUR_DOMAIN.COM/
We now need to re-edit the nginx virtual host config and uncomment the lines about the certificate files.
sudo vi YOUR_DOMAIN.vhost
It should now look like this…
Do the same for
Now restart nignx:
sudo /etc/init.d/nginx restart
TK setup cron to regenerate the SSL cert?
Okay, now let’s jump back to your local machine for a bit to setup the app for development if you don’t already have one.
These instructions assume you are working on a mac and already have Xcode, Swift, as well as Vapor up and working. If you need to install the Vapor toolbox please refer to the Vapor Docs.
terminal app navigate to where you keep your project files. I’ll just use the desktop for this demonstration. Be sure to adjust the
vapor new APP_NAME
Next, add the
mysql-provider to your Package dependencies. Your
Package.swift file should at least contain…
I use Xcode, so I’ll have vapor set up the
vapor xcode --mysql
Now the APP_NAME folder should contain…
My .gitignore file looks like this:
If you are using a
secrets folder to store Config files for things like MySQL you will have to copy/create those files on the remote server before your app will function properly.
You’ll likely have a
Config folder which looks like:
You should make sure to use a different database name between the
beta environments. For Example the
Config/production/mysql.json should look like:
In order to have the
production environments running at the same time you need to make sure each app is bound to a different port. To accomplish this we add a configuration to override the
servers.json Config, and change the port for the
beta. Add a custom
TK also need to somehow configure the app to protect against public access if we’re running the
Adding the remote repositories. By now, hopefully, your DNS has refreshed and you are able to
Let’s make sure you’ll be able to push to the remote server. We can do that by letting ssh know that when you access that address you want to use a specific key. You do this by editing
Add these lines (Words in ALL_CAPS need to be adjusted for your setup)…
Next, insure you are still in your
APP_NAME folder and add the remote git repositories…
git remote add beta ssh://REMOTE_USER_NAME@YOUR_DOMAIN.COM/home/REMOTE_USER_NAME/src/APP_NAME/beta/.git
git remote add production ssh://REMOTE_USER_NAME@YOUR_DOMAIN.COM/home/REMOTE_USER_NAME/src/APP_NAME/production/.git
Now you have a
production repository. You can push out to the
beta when you want to test your app in the real world. And push out to
production when you want it live.
Let’s push out the
beta to make sure everything is working.
git add .
git commit -m 'First commit to test pushing to beta server`
git push beta master
At this point the files should be transferred to the remote
beta repository and the
post-receive hook should run. The project will compile and you’ll see the output from
post-receive ending with
remote: Restarting APP_NAME-beta
You should now be able to go to https://YOUR_DOMAIN.COM and connect with your app.