๐Ÿš€ Building a Simple File Upload Server on AWS EC2 with FastAPI (Amazon Linux 2023)

Learn how to upload photos and videos from your phone directly to an AWS EC2 server using Python and FastAPI.


๐Ÿ“Œ Overview

One of the most common requirements in modern applications is the ability to upload files from a browser to a server.

Whether you’re building:

  • A media management platform
  • A document portal
  • A machine learning application
  • An image processing workflow
  • A video processing pipeline
  • A backup solution

The first step is always the same:

Get the file from the user’s device to your server.

In this tutorial, we’ll build a lightweight file upload application using:

  • AWS EC2
  • Amazon Linux 2023
  • Python
  • FastAPI
  • HTML
  • JavaScript

The result is a mobile-friendly web page that allows users to upload photos and videos directly to an EC2 instance.

Table of Contents


๐Ÿ—๏ธ Final Architecture

Phone / Laptop
       โ”‚
       โ–ผ
FastAPI Web UI
       โ”‚
       โ–ผ
AWS EC2 Instance
       โ”‚
       โ–ผ
uploads/

The workflow is intentionally simple:

  1. User selects a file.
  2. Browser uploads the file.
  3. FastAPI receives the file.
  4. File is saved to disk.

No database required.

No external storage required.

No frontend framework required.


โ˜๏ธ Step 1: Launch an AWS EC2 Instance

Launch a new EC2 instance.

Instance Configuration

  • Amazon Linux 2023
  • t2.nano
  • Public IPv4 Enabled

Create and download a new key pair.

Example:

MediaUploadServer.pem

๐Ÿ”‘ Step 2: Connect to the Server

Connect using SSH:

ssh -i .\MediaUploadServer.pem ec2-user@13.220.113.175

Fixing “Unprotected Private Key” Errors on Windows

If Windows reports that your PEM file permissions are too open, run:

icacls .\MediaUploadServer.pem /inheritance:r
icacls .\MediaUploadServer.pem /grant:r "$($env:USERNAME):(R)"
icacls .\MediaUploadServer.pem /remove "Authenticated Users" "BUILTIN\Users" "Everyone"

Then reconnect.


๐Ÿ”„ Step 3: Update the Server

After logging in:

sudo yum update -y
sudo dnf update -y

Install Python:

sudo yum install python3 -y
sudo yum install python3-pip -y

Verify installation:

python3 --version

๐Ÿ“ Step 4: Create the Project Directory

Create a project folder:

mkdir uploader
cd uploader

๐Ÿ Step 5: Create a Python Virtual Environment

Create a virtual environment:

python3 -m venv venv

Activate it:

source venv/bin/activate

Your terminal should now display:

(venv)

indicating that the virtual environment is active.


Why Use a Virtual Environment?

A virtual environment keeps project dependencies isolated from the operating system.

Without a virtual environment:

System Python
โ”œโ”€โ”€ FastAPI
โ”œโ”€โ”€ Django
โ”œโ”€โ”€ NumPy
โ””โ”€โ”€ Other Projects

With a virtual environment:

uploader/
โ””โ”€โ”€ venv/

Everything stays self-contained.


Verify the Virtual Environment

Before activation:

which python3

Example:

/usr/bin/python3

After activation:

which python

Example:

/home/ec2-user/uploader/venv/bin/python

This confirms that Python commands are now using the virtual environment.


Leaving the Virtual Environment

When finished:

deactivate

Do You Need to Activate It Every Time?

For development:

cd uploader
source venv/bin/activate

Yes.

For production services, you can directly reference the virtual environment’s Python executable:

ExecStart=/home/ec2-user/uploader/venv/bin/uvicorn app:app --host 0.0.0.0 --port 8000

No manual activation required.


๐Ÿ“ฆ Step 6: Install Dependencies

Install FastAPI and related packages:

pip install fastapi uvicorn python-multipart

Packages:

FastAPI

Python web framework.

Uvicorn

ASGI web server.

python-multipart

Required for file uploads.


Where Are Packages Installed?

Because we activated the virtual environment, packages are installed into:

uploader/venv/lib/python3.x/site-packages/

instead of:

/usr/lib/python3.x/

Save Dependencies

Generate a requirements file:

pip freeze > requirements.txt

Project structure:

uploader/
โ”œโ”€โ”€ app.py
โ”œโ”€โ”€ requirements.txt
โ”œโ”€โ”€ uploads/
โ”œโ”€โ”€ static/
โ””โ”€โ”€ venv/

๐Ÿ“‚ Step 7: Create Required Directories

Create storage folders:

mkdir uploads
mkdir static

โš™๏ธ Step 8: Build the Backend

Create:

nano app.py

Paste:

from fastapi import FastAPI, UploadFile, File
from fastapi.responses import FileResponse
import os

app = FastAPI()

UPLOAD_DIR = "uploads"
os.makedirs(UPLOAD_DIR, exist_ok=True)

@app.get("/")
def home():
    return FileResponse("static/index.html")

@app.post("/upload")
async def upload(file: UploadFile = File(...)):
    filepath = os.path.join(UPLOAD_DIR, file.filename)

    with open(filepath, "wb") as f:
        f.write(await file.read())

    return {
        "success": True,
        "filename": file.filename
    }

๐Ÿ“ Nano Editor Tips

To save:

CTRL + O
Enter

To exit:

CTRL + X

Save and exit sequence:

CTRL + O
Enter
CTRL + X

๐ŸŽจ Step 9: Build the Frontend

Create:

nano static/index.html

Paste:

<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Uploader</title>
</head>
<body>

<h2>Upload Photo or Video</h2>

<input type="file" id="file">

<br><br>

<button onclick="uploadFile()">
Upload
</button>

<p id="status"></p>

<script>
async function uploadFile() {
    const file = document.getElementById("file").files[0];

    if (!file) {
        alert("Select a file");
        return;
    }

    const formData = new FormData();
    formData.append("file", file);

    document.getElementById("status").innerText = "Uploading...";

    const response = await fetch("/upload", {
        method: "POST",
        body: formData
    });

    const result = await response.json();

    document.getElementById("status").innerText =
        "Uploaded: " + result.filename;
}
</script>

</body>
</html>

๐Ÿš€ Step 10: Start the Application

Run:

uvicorn app:app --host 0.0.0.0 --port 8000

Expected output:

Uvicorn running on http://0.0.0.0:8000

๐Ÿ”ฅ Step 11: Configure AWS Security Groups

This step is critical.

Open the EC2 Security Group.

Add:

Type: Custom TCP
Port: 8000
Source: 0.0.0.0/0

๐Ÿ› Real-World Debugging Lesson

The application initially failed to load from the browser.

Everything appeared correct:

โœ… FastAPI running

โœ… Uvicorn listening

โœ… Public IP assigned

โœ… Route Table configured

โœ… Network ACL configured

Yet the browser timed out.

After investigating, the issue turned out to be:

Security Group Port: 8080
Application Port:   8000

The Security Group was opening port 8080 while FastAPI was running on port 8000.

Once port 8000 was added to the Security Group, the application became accessible immediately.

A great reminder that networking issues are often caused by small configuration mismatches.


๐Ÿ“ฑ Testing the Application

Open:

http://YOUR_PUBLIC_IP:8000

From:

  • Android
  • iPhone
  • Laptop
  • Tablet

Select a photo or video.

Press Upload.

Success!


๐Ÿ“‚ Verify Uploaded Files

On the server:

ls -lh uploads

Example:

reel+aud.mp4

The uploaded file is now stored on the EC2 instance.


๐Ÿ“ธ Suggested Screenshots

Add screenshots for:

AWS EC2 Instance

[Insert Screenshot]

SSH Connection

[Insert Screenshot]

Virtual Environment

[Insert Screenshot]

FastAPI Running

[Insert Screenshot]

Security Group Configuration

[Insert Screenshot]

Upload Page

[Insert Screenshot]

Successful Upload

[Insert Screenshot]


๐Ÿ”— Source Code

GitHub Repository:

[Insert GitHub Link]


๐Ÿš€ Next Improvements

Once the upload pipeline is working, you can extend it in many ways:

  • Upload progress bars
  • Multiple file uploads
  • Drag-and-drop support
  • Authentication
  • User accounts
  • HTTPS with Nginx
  • Domain names
  • Cloud storage integration
  • Image optimization
  • Video processing
  • AI-powered file analysis

The possibilities are endless.


Final Thoughts

Building a file upload server doesn’t need to be complicated.

With a small EC2 instance, FastAPI, and a few lines of HTML and JavaScript, it’s possible to create a functional upload system in less than an hour.

The final result is a lightweight, mobile-friendly upload service that can serve as the foundation for countless applications and workflows.

Sometimes the best approach is to start with the simplest working version and build from there.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

TechMilestoneHub

Build Skills, Unlock Milestones

ยฉ 2025 TechMilestoneHub


The content on TechMilestoneHub is for educational purposes only and may not always reflect the latest official guidance. Tutorials, quizzes, and examples do not guarantee certification success or specific results. We are not affiliated with certification vendors unless stated. Some pages may contain affiliate links, which may earn us a commission at no extra cost to you. By using this site, you agree to use the information at your own risk. See our Disclaimer, Terms & Conditions, and Privacy Policy for details.