Deploy Django on AWS Lightsail
Deploy Django on an AWS Lightsail instance with a static IP, firewall rules, Gunicorn, Nginx, HTTPS, and an optional Lightsail managed PostgreSQL database.
Problem statement
AWS Lightsail is the simplest way to run a Django app on AWS: a fixed monthly price, a Linux virtual server, a static IP, a basic firewall, snapshots, and optional managed databases, without touching VPCs, security groups, or IAM roles on day one. It suits small production apps, internal tools, and client projects that need to live in an AWS account.
The trap is treating Lightsail like a toy. The instance still needs a real process manager, a reverse proxy, TLS, a production database, and backups. The Lightsail-specific pieces (static IP, the instance firewall, managed database networking, and snapshots) are the parts people most often get wrong. This guide covers them and uses a standard Gunicorn and Nginx stack for the application itself.
- Create an Ubuntu LTS instance in the region where your users and database will live.
- Allocate a static IP and attach it to the instance, then point your domain at that IP.
- In the instance firewall, allow SSH (ideally restricted to your IP), HTTP 80, and HTTPS 443.
- Install Python, create a deploy user and virtual environment, and install the app.
- Use a Lightsail managed PostgreSQL database (or PostgreSQL on the instance) and connect with TLS.
- Run Gunicorn under systemd on a Unix socket and put Nginx in front of it.
- Issue a Let's Encrypt certificate with Certbot, then enable automatic snapshots.
Step-by-step solution
1. Create the instance
In the Lightsail console, create an instance with the OS Only Ubuntu LTS blueprint. Pick a plan with at least 1 GB of RAM for a small app with PostgreSQL elsewhere; 2 GB if PostgreSQL runs on the same instance.
With the AWS CLI, list the current blueprints and bundles rather than guessing their IDs, because AWS versions them:
aws lightsail get-blueprints --query "blueprints[?platform=='LINUX_UNIX'].[blueprintId,name]" --output table
aws lightsail get-bundles --query "bundles[].[bundleId,ramSizeInGb,price]" --output table
aws lightsail create-instances \
--instance-names django-prod \
--availability-zone eu-central-1a \
--blueprint-id <ubuntu-blueprint-id> \
--bundle-id <bundle-id>
2. Attach a static IP
A Lightsail instance's default public IP changes when the instance is stopped and started. Attach a static IP before creating DNS records:
aws lightsail allocate-static-ip --static-ip-name django-prod-ip
aws lightsail attach-static-ip --static-ip-name django-prod-ip --instance-name django-prod
aws lightsail get-static-ip --static-ip-name django-prod-ip --query "staticIp.ipAddress"
Create an A record for example.com and www.example.com pointing at that address. Static IPs are free while attached; an unattached static IP is billed, so release any you no longer use.
3. Configure the Lightsail firewall
The instance firewall is managed by Lightsail, outside the operating system. New instances allow SSH and HTTP by default. Add HTTPS and restrict SSH:
aws lightsail put-instance-public-ports --instance-name django-prod --port-infos \
"fromPort=22,toPort=22,protocol=TCP,cidrs=203.0.113.10/32" \
"fromPort=80,toPort=80,protocol=TCP" \
"fromPort=443,toPort=443,protocol=TCP"
put-instance-public-ports replaces the whole rule set, so list every port you need. IPv4 and IPv6 rules are separate in the console; if the instance has IPv6 enabled, review both.
Do not open PostgreSQL (5432) or Redis (6379) to the internet.
4. Prepare the server
Connect with the key from the Lightsail console (default user ubuntu), then install packages and create a dedicated user:
sudo apt update && sudo apt -y upgrade
sudo apt -y install python3-venv python3-dev build-essential libpq-dev nginx git
sudo adduser --system --group --home /srv/myapp myapp
sudo -u myapp git clone https://github.com/example/myapp.git /srv/myapp/app
sudo -u myapp python3 -m venv /srv/myapp/venv
sudo -u myapp /srv/myapp/venv/bin/pip install -r /srv/myapp/app/requirements.txt gunicorn
Small bundles have little memory. Adding a swap file prevents pip install and collectstatic from being killed during deploys; see Add Swap to a Small Django Server.
5. Choose and connect the database
Option A: Lightsail managed PostgreSQL. Create it in the same region as the instance. By default it is not publicly accessible and is reachable from Lightsail resources in the same region, which is what you want.
aws lightsail get-relational-database-blueprints --query "blueprints[].blueprintId"
aws lightsail get-relational-database-bundles --query "bundles[].[bundleId,ramSizeInGb,price]" --output table
Use the endpoint and master password shown by the console, then create an application database and user so the app does not run as the master user.
Option B: PostgreSQL on the instance. Cheaper, but you own backups, upgrades, and tuning. Use a bundle with enough memory for both.
Put configuration in an environment file readable only by the app user:
sudo install -o root -g myapp -m 640 /dev/null /etc/myapp.env
sudoedit /etc/myapp.env
DJANGO_SETTINGS_MODULE=config.settings.production
DJANGO_SECRET_KEY=replace-with-a-long-random-value
DJANGO_ALLOWED_HOSTS=example.com,www.example.com
DATABASE_URL=postgres://myapp:password@ls-abc123.xxxx.eu-central-1.rds.amazonaws.com:5432/myapp?sslmode=require
Then run migrations and collect static files as the app user with that environment loaded:
sudo -u myapp bash -c 'set -a; . /etc/myapp.env; set +a; cd /srv/myapp/app && \
/srv/myapp/venv/bin/python manage.py migrate --noinput && \
/srv/myapp/venv/bin/python manage.py collectstatic --noinput'
6. Run Gunicorn under systemd
# /etc/systemd/system/gunicorn.service
[Unit]
Description=Gunicorn for myapp
After=network.target
[Service]
User=myapp
Group=www-data
WorkingDirectory=/srv/myapp/app
EnvironmentFile=/etc/myapp.env
RuntimeDirectory=gunicorn
ExecStart=/srv/myapp/venv/bin/gunicorn config.wsgi:application \
--workers 3 --bind unix:/run/gunicorn/gunicorn.sock --access-logfile -
Restart=on-failure
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now gunicorn
7. Configure Nginx and HTTPS
# /etc/nginx/sites-available/myapp
server {
listen 80;
server_name example.com www.example.com;
location /static/ {
alias /srv/myapp/app/staticfiles/;
}
location / {
proxy_pass http://unix:/run/gunicorn/gunicorn.sock;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginx
sudo apt -y install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
In Django production settings, trust the proxy header and enforce HTTPS:
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
CSRF_TRUSTED_ORIGINS = ["https://example.com", "https://www.example.com"]
8. Enable snapshots and verify
Enable automatic snapshots on the instance (and on a managed database, which has its own backup settings). Snapshots are a recovery aid for the server; keep separate database dumps, since restoring a whole instance to recover one table is slow.
Verification check:
curl -sI https://example.com/ | head -1
sudo systemctl status gunicorn --no-pager
sudo -u myapp bash -c 'set -a; . /etc/myapp.env; set +a; cd /srv/myapp/app && /srv/myapp/venv/bin/python manage.py check --deploy'
Explanation
Lightsail bundles compute, a public IP, and a firewall into a fixed price, which removes most of the AWS networking decisions. What it does not change is the application stack: Django still runs under Gunicorn, Nginx still terminates TLS and serves static files, and systemd still keeps processes alive. The static IP and firewall exist outside the server, so they survive rebuilds, and the managed database keeps your data independent of the instance's lifecycle.
Edge cases / notes
Outgrowing Lightsail
Lightsail can peer with a VPC in the same account, which lets you reach RDS, ElastiCache, or other AWS services later without rebuilding. When you need autoscaling, private subnets, or fine-grained IAM, moving to EC2 or ECS is the natural next step.
Load balancers
A Lightsail load balancer can terminate TLS with a Lightsail-managed certificate. If you use one, the instance receives plain HTTP from the balancer; keep SECURE_PROXY_SSL_HEADER and restrict the instance firewall so only the balancer and your SSH IP can reach it.
Rollback
Keep the previous release on disk (a release directory or a Git tag) so a bad deploy is a code switch plus a Gunicorn restart. Take a manual snapshot before risky changes such as OS upgrades.
When to automate this
Once you rebuild or add a second instance, script the steps above: instance creation and firewall rules with the CLI or Terraform, and the server setup and releases with a deploy script or Ansible. The firewall rules, systemd unit, and Nginx site are the most reusable pieces.
Next steps
- Follow the full server walkthrough in Deploy Django with Gunicorn and Nginx on Ubuntu.
- Secure the database connection with Connect Django to Managed PostgreSQL Securely.
- Add or renew certificates with Configure HTTPS for Django with Let's Encrypt.
- If Nginx returns errors, work through Django 502 Bad Gateway: Causes and Fixes.
FAQ
Is AWS Lightsail good enough for production Django?
Yes, for small and medium apps with steady traffic. You still need proper process management, TLS, backups, and monitoring. Move to EC2 or containers when you need autoscaling or private networking.
Why did my Lightsail instance's IP address change?
The default public IP is released when the instance stops. Attach a static IP and point DNS at it.
Can a Lightsail instance connect to a Lightsail database without making it public?
Yes. Managed databases are reachable from Lightsail resources in the same region without enabling public mode. Keep public mode off.
Do I need the AWS CLI to use Lightsail?
No. The console covers everything in this guide. The CLI helps when you want repeatable, scripted setup.