Skip to content

Instantly share code, notes, and snippets.

@ElToro1966
Last active February 16, 2021 22:48
Show Gist options
  • Save ElToro1966/8349e08ad4bda54fbdd9d3e4a345277e to your computer and use it in GitHub Desktop.
Save ElToro1966/8349e08ad4bda54fbdd9d3e4a345277e to your computer and use it in GitHub Desktop.
Typical provisioning of a Django setup with Gunicorn and Nginx

Provisioning a new site

An alternative to the provisioning set out in “Test-Driven Development with Python, 2nd edition, by Harry J.W. Percival (O’Reilly). Copyright 2017 Harry Percival, 978-1-491-95870-4.”. Added provisioning via SSH only (including the GitHub repository), a non-root user for provisioning, and some hardening. Replace SITENAME below with your site's url, e.g., staging.mydomain.com. Replace USERNAME with the user added on the web server, e.g., slartibartfast.

Required packages:

  • Python 3.6
  • pip
  • python3-venv
  • nginx
  • gunicorn
  • git

Create user

Create new user on server:

  • useradd USERNAME
  • usermod -a -G sudo USERNAME
  • mkdir /home/USERNAME
  • mkdir /home/USERNAME/.ssh
  • chmod 700 /home/USERNAME/.ssh
  • passwd USERNAME

Folder and folder permissions

Create a new folder under /srv on server:

  • mkdir /srv/SITENAME
  • chown -R USERNAME /srv/SITENAME
  • chgrp -R www-data /srv/SITENAME
  • chmod -R 750 /srv/SITENAME
  • chmod g+s /srv/SITENAME

Set up public key authentication

  • vim /home/USERNAME/.ssh/authorized_keys

  • add the contents of the id_rsa.pub on the local machine:

    cat /home/USERNAME/.ssh/id_rsa.pub

  • chmod 400 /home/USERNAME/.ssh/authorized_keys

  • chown USERNAME:USERNAME /home/USERNAME -R

Lock down SSH

  • vim /etc/ssh/sshd_config:

    PermitRootLogin no

    PasswordAuthentication no

  • Addition if you have a fixed IP locally:

    AllowUsers deploy@(your-ip) deploy@(another-ip-if-any)

Additional hardening

  • Activate ufw (Ubuntu Firewall):

    ufw allow 22

    ufw allow 80

    ufw allow 443

    ufw enable

  • Alternative for port 22 (ssh) if you have a fixed IP locally:

    ufw allow from {your-ip} to any port 22

  • Install fail2ban:

    apt -y install fail2ban

Note: Feel free to go crazy on the hardening. This is just a minimum setup. A lynis audit is probably a good idea (for starters). Always good to keep practicality in mind, though...

Login with USERNAME

  • service ssh restart
  • logout
  • ssh USERNAME@server-ip

Add server user pub key to GitHub (if using SSH)

$ ssh-keygen -t rsa -b 4096 -C "your_email@example.com"

Before adding a new SSH key to the ssh-agent to manage your keys, you should have checked for existing SSH keys and generated a new SSH key.

  1. Start the ssh-agent in the background.

    $ eval "$(ssh-agent -s)"

    Agent pid 59566

  2. Add your SSH private key to the ssh-agent.

    $ ssh-add ~/.ssh/id_rsa

  3. Add the SSH key to your GitHub account.

Run fabric deployment

OBS: Make sure the server complies with the requirements above before running the script. Install eventual missing software.

On the local machine:

/deploy_tools$ fab deploy:host=USERNAME@SITENAME

The following folder structure should be present on /srv/SITENAME after running the deployment:

srv

└── SITENAME
    |--- .git
    |--- deploy_tools
    |--- functional_tests
    |--- lists
    |--- static
    |--- superlists
    |--- virtualenv
    .env
    .gitignore
    README.md
    db.sqlite3
    manage.py
    requirements.txt

Nginx Virtual Host config

  • cat ./deploy_tools/nginx.template.conf \

    | sed "s/DOMAIN/SITENAME/g" \

    | sudo tee /etc/nginx/sites-available/SITENAME

  • The result should be:

	server {
		listen 80;
		server_name SITENAME;
		location /static {
			alias /srv/SITENAME/static;
		}
		location / {
			proxy_set_header Host $host;
			proxy_pass http://unix:/tmp/SITENAME.socket;
		
		}
		
	}
  • Add link in /etc/nginx/sites-enabled/SITENAME:

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

  • Remove 'default' from /etc/nginx/sites-enabled:

    rm /etc/nginx/sites-enabled/default

  • Enable and start nginxi (reload if already started):

    sudo systemctl daemon-reload

    sudo systemctl enable nginx

    sudo systemctl start nginx (or reload)

Gunicorn systemd service

  • cat ./deploy_tools/gunicorn-systemd.template.service \

    | sed "s/DOMAIN/SITENAME/g" | sed "s/USERNAME/YOUR_USERNAME/g" \

    | sudo tee /etc/systemd/system/gunicorn-SITENAME.service

  • The result should be:
	[Unit]
	Description=Gunicorn server for SITENAME
	
	[Service]
	Restart=on-failure
	User=YOUR_USERNAME
	Group=www-data
	WorkingDirectory=/srv/SITENAME
	EnvironmentFile=/srv/SITENAME/.env
	
	ExecStart=/srv/SITENAME/virtualenv/bin/gunicorn \
    	--bind unix:/tmp/SITENAME.socket \
    	superlists.wsgi:application
	
	[Install]
	
	WantedBy=multi-user.target

OBS: Note the User and Group definitions. Must be inluded in the template.

  • Enable and start gunicorn:

    sudo systemctl enable gunicorn-SITENAME.service

    sudo systemctl start gunicorn-SITENAME.service

Sources and more info

H.J.W Percival's Test_driven Development In Python, Ch.10 https://www.obeythetestinggoat.com/book/chapter_making_deployment_production_ready.html

My First 5 Minutes With A Server https://plusbryan.com/my-first-5-minutes-on-a-server-or-essential-security-for-linux-servers

GitHub: Generating a new SSH key on the server and adding it to the ssh-agent: https://help.github.com/en/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent

Superuser-com on directory structures on web servers: https://superuser.com/questions/635289/what-is-the-recommended-directory-to-store-website-content

What permissions should my website files/folders have on a Linux webserver? https://serverfault.com/questions/357108/what-permissions-should-my-website-files-folders-have-on-a-linux-webserver#357109

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