This guide walks you through deploying a Python Flask application on an AWS EC2 instance (specifically Amazon Linux 2). We’ll use Gunicorn as the application server and Nginx as a high-performance reverse proxy. This setup is robust, secure, and standard for production environments.
For developers working on different platforms, you might also be interested in exposing Flask apps on WSL to local networks, which complements this cloud deployment approach.
The Architecture: How It All Works
Before we begin, it’s crucial to understand the roles of each component:
- Flask App: Your Python code that runs the web application. It can only handle one request at a time on its own.
- Gunicorn: The application server. It communicates with your Flask app, runs multiple worker processes, and handles multiple incoming requests simultaneously. It serves your app internally on a specific port (e.g., 5000).
- Nginx: The web server and reverse proxy. It faces the public internet, listens on standard ports (80 for HTTP), handles incoming traffic efficiently, and forwards the requests to Gunicorn. It can also serve static files and manage SSL/TLS encryption.
- Systemd: The Linux service manager. It ensures your Gunicorn process starts automatically on boot and restarts if it ever crashes.
- AWS Security Group: The virtual firewall for your EC2 instance, controlling what traffic is allowed in and out.
The flow of a user request looks like this: User’s Browser → Port 80 (Nginx) → Port 5000 (Gunicorn) → Your Flask App
This architecture pattern is part of broader system design trade-offs where we balance performance, scalability, and operational complexity. For more context on deployment patterns and infrastructure decisions, see our guide on choosing databases by requirements.
Step 1: Prepare Your Flask Application and Gunicorn
First, ensure your application is running correctly with Gunicorn.
-
Install Gunicorn: SSH into your EC2 instance and install Gunicorn in your Python environment.
pip install gunicorn
-
Test Gunicorn Manually: From your project directory, run Gunicorn directly to make sure it can serve your app.
app:app
refers to theapp
object inside theapp.py
file. Adjust if your filenames or variable names are different.gunicorn --workers 3 --bind 0.0.0.0:5000 app:app
This command tells Gunicorn to start 3 worker processes and listen for requests on port 5000 on all available network interfaces (
0.0.0.0
). You can stop it withCtrl+C
.
Step 2: Create a systemd
Service for Gunicorn
To ensure your application runs as a background service, we’ll use systemd
.
-
Create a Service File:
sudo nano /etc/systemd/system/myapp.service
-
Add the Service Configuration: Paste the following configuration. Remember to replace
ec2-user
with your actual user if it’s different and update the paths to match your project structure.[Unit] Description=Gunicorn instance to serve myapp After=network.target [Service] User=ec2-user Group=ec2-user WorkingDirectory=/home/ec2-user/your-project-directory ExecStart=/home/ec2-user/.local/bin/gunicorn --workers 3 --bind 0.0.0.0:5000 app:app [Install] WantedBy=multi-user.target
-
Start and Enable the Service:
sudo systemctl start myapp.service sudo systemctl enable myapp.service
-
Crucial Verification: At this point, your backend should be fully operational. Verify this from within the EC2 instance.
curl http://127.0.0.1:5000
You should see the HTML of your application. If this fails, check the service status for errors:
sudo systemctl status myapp.service
.
Step 3: Configure Nginx as a Reverse Proxy
This is where we bridge the public internet (port 80) to your application (port 5000).
-
Install Nginx:
sudo amazon-linux-extras install nginx1 -y
-
Understand Nginx Configuration on Amazon Linux: Unlike Debian/Ubuntu, Amazon Linux (and other Red Hat-based systems) does not use the
sites-available
/sites-enabled
structure by default.- The main configuration is
/etc/nginx/nginx.conf
. - This file loads all additional configurations from the
/etc/nginx/conf.d/
directory. - Crucially,
/etc/nginx/nginx.conf
contains a default server block that we must disable.
- The main configuration is
-
Create Your App’s Nginx Configuration: Create your configuration file directly inside the
/etc/nginx/conf.d/
directory.sudo nano /etc/nginx/conf.d/myapp.conf
Paste the following. This configuration listens on port 80 and proxies all requests to Gunicorn.
server { listen 80; server_name _; # This acts as a catch-all server name location / { proxy_pass http://127.0.0.1:5000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }
-
Disable the Default Nginx Server: This is the critical step that we discovered. You must comment out the default server block inside the main
nginx.conf
file to prevent it from conflicting with yourmyapp.conf
.sudo nano /etc/nginx/nginx.conf
Find the default
server
block (around line 38) and add a#
to the beginning of every line in that section.# server { # listen 80; # listen [::]:80; # server_name _; # root /usr/share/nginx/html; # ...and so on... # }
Step 4: Configure the AWS Security Group
Your EC2 firewall must allow public traffic to Nginx.
- Go to the AWS EC2 Console and find the Security Group for your instance.
- Select the “Inbound rules” tab and click “Edit inbound rules”.
- Ensure you have rules to Allow traffic for:
- Type:
HTTP
, Port:80
, Source:Anywhere (0.0.0.0/0)
- Type:
SSH
, Port:22
, Source:My IP
(for your security)
- Type:
- Important: You do not need a rule for port 5000. That port should only be accessed internally, and Nginx handles that. Keeping it closed to the public is more secure.
For production deployments, consider implementing additional AWS services like SNS for messaging and exploring database options depending on your application’s requirements.
Step 5: Launch and Final Test
Apply all your Nginx changes and test the final result.
-
Test Nginx Configuration Syntax:
sudo nginx -t
It must report that the syntax is
ok
and the test issuccessful
. -
Restart Nginx to Apply Changes:
sudo systemctl restart nginx
-
Test in Your Browser: Open a new private/incognito browser window and navigate to your EC2 instance’s public IP address (
http://<your-public-ip-address>
). Your application should now be live!
Scaling and Next Steps
Once your basic deployment is working, consider these enhancements for production:
- Container Orchestration: For larger applications, explore Kubernetes deployments with monitoring via Prometheus and Grafana
- Database Strategy: Review our database selection guide to optimize your data layer
- Monitoring: Implement comprehensive monitoring similar to the IoT dashboard project for system health tracking
- System Design: Apply system design principles to plan for growth and scalability
Quick Troubleshooting Reference
-
Problem: Browser says “Connection Timed Out”.
- Cause: Your Security Group is likely blocking traffic. Check that port 80 is open to
0.0.0.0/0
.
- Cause: Your Security Group is likely blocking traffic. Check that port 80 is open to
-
Problem: You see the “Welcome to Nginx!” page.
- Cause: Nginx is working, but your
myapp.conf
is being ignored. - Solution: Ensure you have commented out the default
server
block in/etc/nginx/nginx.conf
and that yourmyapp.conf
is inside/etc/nginx/conf.d/
. Restart Nginx after making changes.
- Cause: Nginx is working, but your
-
Problem: Browser says “502 Bad Gateway”.
- Cause: Nginx is working and proxying, but it can’t connect to Gunicorn.
- Solution: Your
systemd
service for Gunicorn has likely failed. Check its status withsudo systemctl status myapp.service
and check the logs withsudo journalctl -u myapp.service
. Also, confirm withcurl http://127.0.0.1:5000
.
Related Articles
- Exposing Flask Apps on WSL to Local Networks - Local development deployment patterns
- AWS SNS Messaging Guide - Integrating AWS messaging services
- System Design Trade-offs - Understanding deployment architecture decisions
- Cloud-Native Deployment with Kubernetes - Advanced container orchestration
- Database Selection Guide - Choosing the right database for your deployment