This project provides a complete guide and configuration scripts for setting up a secure Linux server on AWS EC2 . It walks through every step required to transform a fresh EC2 instance into a production-ready environment capable of hosting web applications.
The configuration covers:
- π Security Hardening β User management, SSH key authentication, firewall (UFW) setup, and disabling root login.
- βοΈ Server Setup β Package updates, timezone configuration, Apache2 + WSGI setup, PostgreSQL installation, and Python dependencies.
- π Web Deployment β Cloning a Flask application, configuring Apache Virtual Hosts for HTTP/HTTPS, enabling SSL, and setting up PostgreSQL for persistence.
- π Automation & Monitoring β Essential commands, log checks, and configurations to keep the server stable and secure.
This repository is ideal for students, developers, and system administrators who want a ready-made reference for deploying Flask or other Python web apps on a hardened Linux environment with AWS EC2 .
- Create EC2 Account.
- Create EC2 instance.
sudo apt-get update
sudo apt-get upgrade
sudo reboot
sudo adduser grader
-
Create file in given directory with name "grader"
sudo touch /etc/sudoers.d/grader
sudo nano /etc/sudoers.d/grader
-
Type:
grader ALL=(ALL) NOPASSWD:ALL
-
ctrl-o
to save. -
ctrl-x
to exit.
sudo su grader
-
Generate an SSH Key on local Machine.
ssh-keygen -t rsa -b 4096 -C <your_email@example.com>
Note the filename and file location used (I used the default that was created at .ssh/id_rsa). When prompted, create a secure passphrase for your SSH key (do not share or document your passphrase).
-
Make new directory after login to the "grader".
sudo mkdir .ssh
-
Create file authorized_keys in .ssh directory.
sudo touch .ssh/authorized_keys
-
Edit authorized_keys
sudo nano .ssh/authorized_keys
Copy public key from local machine (.ssh/id_rsa.pub) and paste into .ssh/authorized_keys file on remote machine.
sudo chmod 700 .ssh
sudo chmod 644 .ssh/authorized_keys
sudo chown grader .ssh
sudo chgrp grader .ssh
sudo chown grader .ssh/authorized_keys
sudo chgrp grader .ssh/authorized_keys
sudo service ssh restart
-
So we can access the server locally by downloading the SSH key pairs provided inside AWS account
ssh ubuntu@<public-ip> -i <key.pem> -p 2200
-
But now login as user "grader" locally
ssh grader@<public-ip> -i .ssh/id_rsa -p 2200
sudo nano /etc/ssh/sshd_config
- Change:
PasswordAuthentication
tono
. ctrl-o
to save.ctrl-x
to exit.
-
Enter the following commands to configure defaults:
sudo ufw default deny incoming sudo ufw default allow outgoing
-
Enter the following to allow/deny only specified ports:
sudo ufw allow ssh sudo ufw allow 2200/tcp sudo ufw allow 80/tcp sudo ufw allow 123/udp sudo ufw allow 443/tcp sudo ufw deny 22/tcp
-
Before enable Firewall make sure port
22
is disabled:sudo nano /etc/ssh/sshd_config
-
Open editor and change port number from
22
to2200
, setPermitRootLogin
tono
.sudo ufw enable sudo service ufw restart sudo ufw status
Note: If using Amazon EC2, you must configure both the AWS Security Groups (in the AWS console) and the server's UFW firewall to allow the same ports (e.g., 2200, 80, 443, 123) for proper connectivity.
-
Open linux time zone configuration:
sudo dpkg-reconfigure tzdata
-
Navigate and Select
None of the above
-
Navigate and Select
UTC
sudo apt-get install git
sudo apt-get install python3-pip
sudo apt-get install apache2
sudo apt-get install libapache2-mod-wsgi-py3
sudo apt-get install postgresql
# (Recommended) Create and activate a Python virtual environment for your project:
python3 -m venv <venv-name>
source <venv-name>/bin/activate
# Upgrade pip and install Python dependencies inside the virtual environment:
pip install --upgrade pip
pip install flask
pip install SQLAlchemy
pip install oauth2client
pip install passlib
pip install requests
pip install psycopg2
When you are finished working on your project, you can deactivate the virtual environment:
deactivate
π Step 16: Clone the Application Repository
-
Change the directory.
cd /var/www
-
Inside that directory run:
sudo git clone https://github.com/fix8developer/udacity-buid-an-item-catalog-application.git catalog
-
Get inside the clone repository.
cd /var/www/catalog
-
Create new project.wsgi file inside the downloaded repository which will serve my flask application.
sudo touch /var/www/catalog/project.wsgi
sudo nano /var/www/catalog/project.wsgi
-
Add the following content
import sys sys.path.insert(0, "/var/www/catalog") from project import app as application
"from project" phrase is actually the name of my main python file.
sudo touch /etc/apache2/sites-available/catalog.conf
sudo nano /etc/apache2/sites-available/catalog.conf
-
Add the following content:
<VirtualHost *:80> ServerName <public-ip/localhost> ServerAdmin kashifiqbal23@gmail.com WSGIScriptAlias / /var/www/catalog/project.wsgi <Directory /var/www/catalog> Require all granted WSGIApplicationGroup %{GLOBAL} WSGIScriptReloading On </Directory> ErrorLog ${APACHE_LOG_DIR}/catalog_error.log CustomLog ${APACHE_LOG_DIR}/catalog_access.log combined </VirtualHost>
sudo touch /etc/apache2/sites-available/catalog-ssl.conf
sudo nano /etc/apache2/sites-available/catalog-ssl.conf
-
Add the following content
<IfModule mod_ssl.c> <VirtualHost *:443> ServerName <public-ip/localhost> ServerAdmin fix8developer@gmail.com WSGIScriptAlias / /var/www/catalog/project.wsgi <Directory /var/www/catalog> Require all granted WSGIApplicationGroup %{GLOBAL} WSGIScriptReloading On </Directory> SSLEngine on SSLCertificateFile /etc/ssl/certs/selfsigned.crt SSLCertificateKeyFile /etc/ssl/private/selfsigned.key ErrorLog ${APACHE_LOG_DIR}/catalog_ssl_error.log CustomLog ${APACHE_LOG_DIR}/catalog_ssl_access.log combined </VirtualHost> </IfModule>
sudo nano /etc/apache2/sites-available/catalog.conf
-
Content
<VirtualHost *:80> ServerName <public-ip/localhost> Redirect permanent / https://<public-ip/localhost>/ </VirtualHost>
sudo apt-get update
sudo apt-get install -y openssl
openssl genrsa -out selfsigned.key 2048
openssl req -new -key selfsigned.key -out selfsigned.csr
Common Name should be your domain (or public IP if no domain).
openssl x509 -req -days 365 -in selfsigned.csr -signkey selfsigned.key -out selfsigned.crt
This creates
selfsigned.crt
(certificate) valid for 1 year.
sudo cp selfsigned.crt /etc/ssl/certs/
sudo cp selfsigned.key /etc/ssl/private/
Set proper permissions:
sudo chmod 600 /etc/ssl/private/selfsigned.key
Disable the default Apache site and enable your flask app.
-
Disable the default configuration file:
sudo a2dissite 000-default.conf
-
Enable the catalog.conf (Flask app configuration for HTTP):
sudo a2ensite catalog.conf sudo a2enmod wsgi
-
Enable the catalog-ssl.conf (Flask app configuration for HTTPS):
sudo a2enmod ssl sudo a2ensite catalog-ssl.conf
-
To active the new configuration we need to run:
sudo systemctl reload apache2
If the application was cloned from (build-an-item-catalog-application), the following modifications are required:
-
Edit the project.py file and move the app.secret_key out of ...
if __name__ == '__main__': app.secret_key = 'super_secret_key' app.run()
-- by moving it to the following line:
app = Flask(__name__) app.secret_key = 'super_secret_key'
-
Also update the path to client_secrets.json in
project.py
to use the absolute file path (e.g.,/var/www/catalog/client_secrets.json
), since the working directory on the remote machine is different from your local machine.CLIENT_ID = json.loads( open('client_secrets.json', 'r').read())['web']['client_id']
-- to this form:
CLIENT_ID = json.loads( open('/var/www/catalog/client_secrets.json', 'r').read())['web']['client_id']
Edit project.py, database_setup.py in clone repository to use postgresql database instead of sqlite
# engine = create_engine('sqlite:///catalog.db')
engine = create_engine(
'postgresql+psycopg2://catalog:catalog@localhost/catalog')
Create database user "catalog"
sudo -u postgres psql postgres
CREATE DATABASE catalog;
CREATE USER catalog;
ALTER ROLE catalog with PASSWORD 'catalog';
GRANT ALL PRIVILEGES ON DATABASE catalog TO catalog;
\q
To view server side error for HTTP:
sudo tail -n 30 /var/log/apache2/catalog_error.log
To view server side error for HTTPS:
sudo tail -n 30 /var/log/apache2/catalog_ssl_error.log
To view server side error for ALL:
sudo tail /var/log/apache2/error.log
Linux Server Configuration is Copyright Β©οΈ 2025 Kashif Iqbal. It is free, and may be redistributed under the terms specified in the LICENSE file.