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
- ๐ Overview
- ๐๏ธ Final Architecture
- โ๏ธ Step 1: Launch an AWS EC2 Instance
- ๐ Step 2: Connect to the Server
- ๐ Step 3: Update the Server
- ๐ Step 4: Create the Project Directory
- ๐ Step 5: Create a Python Virtual Environment
- ๐ฆ Step 6: Install Dependencies
- ๐ Step 7: Create Required Directories
- โ๏ธ Step 8: Build the Backend
- ๐จ Step 9: Build the Frontend
- ๐ Step 10: Start the Application
- ๐ฅ Step 11: Configure AWS Security Groups
- ๐ Real-World Debugging Lesson
- ๐ฑ Testing the Application
- ๐ Verify Uploaded Files
- ๐ธ Suggested Screenshots
- ๐ Source Code
- ๐ Next Improvements
- Final Thoughts
๐๏ธ Final Architecture
Phone / Laptop
โ
โผ
FastAPI Web UI
โ
โผ
AWS EC2 Instance
โ
โผ
uploads/
The workflow is intentionally simple:
- User selects a file.
- Browser uploads the file.
- FastAPI receives the file.
- 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.

Leave a Reply