Mr. Editor-in-chief Mr. Editor-in-chief December 10, 2023 Updated April 24, 2026

Deploy Django on Ubuntu with Caddy && Gunicorn

Pre-setup

  • Make the necessary preparation so that we can open the site on a browser outside the host machine with http://host_ip_address:8000 when we run python3 manage.py runserver 0.0.0.0:8000 on the host machine terminal
  • Install Gunicorn in the virtual environment
pip3 install gunicorn
  • Create a gunicorn configuration in your django project folder(e.g. ~/mdblog/gunicorn.conf.py)
bind = "localhost:8000"
workers = 4
keepalive = 5

wsgi_app = "blog.wsgi" # Replace 'blog' with your main app (the name of the directory that contains wsgi.py)
  • Create a systemd unit sudo nano /etc/systemd/system/gunicorn.service
[Install]
WantedBy=multi-user.target

[Unit]
Description=Gunicorn service
After=network.target

[Service]
WorkingDirectory=/home/ubuntu/mdblog   # /path/to/django_project/  
ExecStart=/home/ubuntu/mdblog/venv/bin/gunicorn # /path/to/django_project_venv/bin/gunicorn
  • Test Gunicorn
sudo systemctl enable gunicorn
sudo systemctl start gunicorn
sudo systemctl status gunicorn

Install Caddy && Config CaddyFile

  • Cady Installation
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy
  • Caddy Configuration sudo nano /etc/caddy/Caddyfile
mdblog.peng.works {
  handle_path /static/* {
        root * /home/ec2-user/mdblog/static/
        file_server
  }
  handle_path /media/* {
        root * /home/ec2-user/mdblog/media/
        file_server
  }
  reverse_proxy localhost:8000
}
node-app.peng.works {
    reverse_proxy localhost:5000
}
# Refer to the Caddy docs for more information:
# https://caddyserver.com/docs/caddyfile
  • Restart Caddy
sudo systemctl restart caddy
sudo systemctl status caddy
  • Note: Make sure the port 443 is open and 'mdblog.peng.works' is in the ALLOWED_HOSTS in Django settings.py if there is a problem opening the site. Don't forget to set debug to False and close port 8000 after it works.

Bonus: Nginx Configuration

  • sudo cp /etc/nginx/sites-available/default /etc/nginx/sites-available/mdblog.conf
  • sudo cp -r ~/mdblog/static /var/www/static
  • sudo nano /etc/nginx/sites-available/mdblog.conf
server {
    server_name 54.168.187.222 peng.works www.peng.works;

    location /static/ {
        root /var/www;
    }

    location / {
        include proxy_params;
        proxy_pass http://localhost:8000;
    }
}
  • sudo ln -s /etc/nginx/sites-available/mdblog.conf /etc/nginx/sites-enabled/mdblog.conf
  • sudo nginx -t
  • sudo systemctl restart nginx
  • You should be able to open the site on a browser outside the host machine with http://54.168.187.222. If you have pointed your.domain.com to this IP address and it has taken effect, you should also be able to open http://peng.works or http://www.peng.works.
  • Assuming that we have uploaded ZeroSSL files(certificate.crt, ca_bundle.crt and private.key) onto the server now.
  • mv certificate.crt certificate_old.crt && cat certificate_old.crt ca_bundle.crt >> certificate.crt # Merge .crt Files for Nginx
  • sudo mv certificate.crt /etc/ssl/certs/ && sudo mv private.key /etc/ssl/private/
  • sudo nano /etc/nginx/sites-available/mdblog.conf
server {
    server_name peng.works www.peng.works; 
    return 301 https://$server_name$request_uri;
}


server {

    listen 443 ssl;
    ssl_certificate /etc/ssl/certs/certificate.crt; 
    ssl_certificate_key  /etc/ssl/private/private.key;

    server_name peng.works www.peng.works;

    location /static/ {
        root /var/www;
    }

    location / {
        include proxy_params;
        proxy_pass http://localhost:8000;
    }
}
  • sudo systemctl restart nginx # Restart the Nginx service and it should work now.