Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
Initial Base Ubuntu Server Setup

Simple Ubuntu Server Setup

Create a user

NOTE: You must use public key based authentication.

  1. Create user adduser testuser
  2. Grant sudo usermod -aG sudo testuser
  3. ufw enable/disable a. ufw disable b. ufw add OpenSSH and ufw enable and whatever else you want.
  4. Run the following command to allow your new user to log in:
rsync --archive --chown=testuser:testuser ~/.ssh /home/testuser

Setup Python, Nginx, Postgres, Gunicorn and other apps

  1. Run sudo apt update
  2. Run sudo apt install -y python3-pip python3-dev libpq-dev postgresql postgresql-contrib nginx curl
  3. Log in to Postgres: sudo -u postgres psql
  4. Create DB: CREATE DATABASE myproject;
  5. CREATE USER testuser WITH PASSWORD 'password';
  6. Set the following:
ALTER ROLE testuser SET client_encoding TO 'utf8';
ALTER ROLE testuser SET default_transaction_isolation TO 'read committed';
ALTER ROLE testuser SET timezone TO 'UTC';
  1. Type \q
  2. sudo -H pip3 install --uprgade pip
  3. sudo -H pip3 install virtualenv
  4. Create project directory: mkdir ~/myprojects/ && cd ~/myprojects
  5. Run virtualenv: virtualenv myprojectenv
  6. Run source /myprojects/myprojectenv/bin/activate
  7. Install more packages: pip install django gunicorn psycopg2-binary
  8. Setup Django: startproject myproject ~/myprojects/myproject

Configure Django

Configuration is stored in the file ~/myprojects/ Update the section with the relevant info:

ALLOWED_HOSTS = ['your_server_domain_or_IP', 'second_domain_or_IP', . . ., 'localhost']

Configure Django to use the psycopg2 lib from pip we installed above:

    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'myproject',
        'USER': 'myprojectuser',
        'PASSWORD': 'password',
        'HOST': 'localhost',
        'PORT': '',

Next, move down to the bottom of the file and add a setting indicating where the static files should be placed. This is necessary so that Nginx can handle requests for these items. The following line tells Django to place them in a directory called static in the base project directory:

STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static/')

Now, we can migrate the initial database schema to our PostgreSQL database using the management script:

~/myprojects/ makemigrations
~/myprojects/ migrate

Create an admin:

~/myprojectdir/ createsuperuser

We can collect all of the static content into the directory location we configured by typing:

~/myprojectdir/ collectstatic

You will have to confirm the operation. The static files will then be placed in a directory called static within your project directory.

sudo ufw allow 8000
~/myprojectdir/ runserver

Open a browser to servername:8000 and servername:8000/admin

The last thing we want to do before leaving our virtual environment is test Gunicorn to make sure that it can serve the application. We can do this by entering our project directory and using gunicorn to load the project’s WSGI module:

cd ~/myprojects/myproject
gunicorn --bind myproject.wsgi

Creating systemd Socket and Service Files for Gunicorn

  1. Create or update the file sudo nano /etc/systemd/system/gunicorn.socket to look like this:
Description=gunicorn socket


  1. Next, create and open a systemd service file for Gunicorn with sudo privileges in your text editor. The service filename should match the socket filename with the exception of the extension sudo nano /etc/systemd/system/gunicorn.service:
Description=gunicorn daemon

ExecStart=/home/testuser/myprojectdir/myprojectenv/bin/gunicorn \
          --access-logfile - \
          --workers 3 \
          --bind unix:/run/gunicorn.sock \

  1. Configure systemd
sudo systemctl start gunicorn.socket
sudo systemctl enable gunicorn.socket
  1. Check the status of the process to find out whether it was able to start:
sudo systemctl status gunicorn.socket

Next, check for the existence of the gunicorn.sock file within the /run directory:

file /run/gunicorn.sock

NOTE: If there are any errors starting gunicorn, run sudo journalctl -u gunicorn.socket Also, check ``

Currently, if you’ve only started the gunicorn.socket unit, the gunicorn.service will not be active yet since the socket has not yet received any connections. You can check this by typing:

sudo systemctl status gunicorn

● gunicorn.service - gunicorn daemon
   Loaded: loaded (/etc/systemd/system/gunicorn.service; disabled; vendor preset: enabled)
   Active: inactive (dead)

To test the socket activation mechanism, we can send a connection to the socket through curl by typing:

curl --unix-socket /run/gunicorn.sock localhost

You should see the HTML output from your application in the terminal. This indicates that Gunicorn was started and was able to serve your Django application. You can verify that the Gunicorn service is running by typing:

sudo systemctl status gunicorn

If the output from curl or the output of systemctl status indicates that a problem occurred, check the logs for additional details:

sudo journalctl -u gunicorn

Check your /etc/systemd/system/gunicorn.service file for problems. If you make changes to the /etc/systemd/system/gunicorn.service file, reload the daemon to reread the service definition and restart the Gunicorn process by typing:

sudo systemctl daemon-reload
sudo systemctl restart gunicorn

Make sure you troubleshoot the above issues before continuing.

Configure Nginx to Proxy Pass to Gunicorn

Start by creating and opening a new server block in Nginx’s sites-available directory:

sudo nano /etc/nginx/sites-available/myproject

Inside, open up a new server block. We will start by specifying that this block should listen on the normal port 80 and that it should respond to our server’s domain name or IP address:

server {
    listen 80;
    server_name server_domain_or_IP;

Next, we will tell Nginx to ignore any problems with finding a favicon. We will also tell it where to find the static assets that we collected in our ~/myprojectdir/static directory. All of these files have a standard URI prefix of “/static”, so we can create a location block to match those requests:

server {
    listen 80;
    server_name server_domain_or_IP;

    location = /favicon.ico { access_log off; log_not_found off; }
    location /static/ {
        root /home/sammy/myprojectdir;

Finally, we’ll create a location / {} block to match all other requests. Inside of this location, we’ll include the standard proxy_params file included with the Nginx installation and then we will pass the traffic directly to the Gunicorn socket:

server {
    listen 80;
    server_name server_domain_or_IP;

    location = /favicon.ico { access_log off; log_not_found off; }
    location /static/ {
        root /home/sammy/myprojectdir;

    location / {
        include proxy_params;
        proxy_pass http://unix:/run/gunicorn.sock;

Save and close the file when you are finished. Now, we can enable the file by linking it to the sites-enabled directory:

sudo ln -s /etc/nginx/sites-available/myproject /etc/nginx/sites-enabled

Test your Nginx configuration for syntax errors by typing:

sudo nginx -t

If no errors are reported, go ahead and restart Nginx by typing:

sudo systemctl restart nginx

Finally, we need to open up our firewall to normal traffic on port 80. Since we no longer need access to the development server, we can remove the rule to open port 8000 as well:

sudo ufw delete allow 8000
sudo ufw allow 'Nginx Full'

You should now be able to go to your server’s domain or IP address to view your application.

Next Steps

  1. sudo add-apt-repository ppa:certbot/certbot
  2. sudo apt install python-certbot-nginx
  3. Check that the server_name attribute is filled in in the Nginx sites-available configuration. For example:
  1. Test config changes sudo nginx -t
  2. sudo systemctl reload nginx
  3. Update ufw:
sudo ufw allow 'Nginx Full'
sudo ufw delete allow 'Nginx HTTP'
sudo certbot --nginx -d -d
  1. Test renewal functionality:
sudo certbot renew --dry-run


  • Check the Nginx process logs by typing: sudo journalctl -u nginx
  • Check the Nginx access logs by typing: sudo less /var/log/nginx/access.log
  • Check the Nginx error logs by typing: sudo less /var/log/nginx/error.log
  • Check the Gunicorn application logs by typing: sudo journalctl -u gunicorn
  • Check the Gunicorn socket logs by typing: sudo journalctl -u gunicorn.socket


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment