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.

Django on AWS LightsailStatic IPstable DNS targetNginxTLS, static filesGunicornDjango appManaged Postgresprivate endpointAttach the static IP before pointing DNS at the instance, or a reboot can change the address.
  1. Create an Ubuntu LTS instance in the region where your users and database will live.
  2. Allocate a static IP and attach it to the instance, then point your domain at that IP.
  3. In the instance firewall, allow SSH (ideally restricted to your IP), HTTP 80, and HTTPS 443.
  4. Install Python, create a deploy user and virtual environment, and install the app.
  5. Use a Lightsail managed PostgreSQL database (or PostgreSQL on the instance) and connect with TLS.
  6. Run Gunicorn under systemd on a Unix socket and put Nginx in front of it.
  7. 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"
the instance firewall, port by portAllow fromVerdictSSH 22your IP only (/32)✓open, restrictedHTTP 80anywhere✓open, redirects to HTTPSHTTPS 443anywhere✓openPostgreSQL 5432nowhere✕never publicRedis 6379nowhere✕never publicput-instance-public-ports replaces the whole rule set, so list every portdjango-deployment.com
Only the web ports face the internet; SSH is limited to your address and data services stay closed.

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

where should the database live?Where does PostgreSQL runfor this Lightsail app?tight budgetOn the instanceyou own backupsreal user dataLightsail managed DBbackups includedneeds AWS servicesRDS via VPC peeringmore setupManaged is the default once users depend on the datadjango-deployment.com
The managed option keeps your data independent of the instance, which is what makes rebuilds routine.

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.

what Lightsail gives you, and what it does notStatic IP + firewallLightsailoutside the OS, survives rebuildsSnapshotsLightsailwhole-instance recoveryNginx + TLSyouCertbot renews certificatesGunicorn + systemdyourestarts, workers, socketDjango settingsyouALLOWED_HOSTS, HTTPS, secretsThe platform removes networking decisions, not the application stackdjango-deployment.com
Everything below the Lightsail layers is the same work as on any Linux server.

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

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.

Keep going