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.

  1. Install Gunicorn: SSH into your EC2 instance and install Gunicorn in your Python environment.

    pip install gunicorn
  2. Test Gunicorn Manually: From your project directory, run Gunicorn directly to make sure it can serve your app. app:app refers to the app object inside the app.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 with Ctrl+C.

Step 2: Create a systemd Service for Gunicorn

To ensure your application runs as a background service, we’ll use systemd.

  1. Create a Service File:

    sudo nano /etc/systemd/system/myapp.service
  2. 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
  3. Start and Enable the Service:

    sudo systemctl start myapp.service
    sudo systemctl enable myapp.service
  4. 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).

  1. Install Nginx:

    sudo amazon-linux-extras install nginx1 -y
  2. 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.
  3. 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;
        }
    }
  4. 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 your myapp.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.

  1. Go to the AWS EC2 Console and find the Security Group for your instance.
  2. Select the “Inbound rules” tab and click “Edit inbound rules”.
  3. 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)
  4. 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.

  1. Test Nginx Configuration Syntax:

    sudo nginx -t

    It must report that the syntax is ok and the test is successful.

  2. Restart Nginx to Apply Changes:

    sudo systemctl restart nginx
  3. 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:

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.
  • 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 your myapp.conf is inside /etc/nginx/conf.d/. Restart Nginx after making changes.
  • 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 with sudo systemctl status myapp.service and check the logs with sudo journalctl -u myapp.service. Also, confirm with curl http://127.0.0.1:5000.