π Building a Secure Web Application Stack with Apache, PostgreSQL, Keycloak, and Firewalld

π Introduction
In todayβs cloud-native environments, application security is more than just HTTPS. Strong authentication, network segmentation, encryption, and access control are all critical to protecting infrastructure.
This article walks through the end-to-end setup of a secure Linux-based application stack:
A jump host for controlled SSH access
An application host running Apache, PHP, PostgreSQL, and Keycloak
Letβs Encrypt SSL for secure HTTPS with automatic redirects
OIDC authentication with Keycloak to protect the web app
TOR exit node blocking using
firewalld
+ipset
Audit and SSH hardening for compliance
The project demonstrates how to combine system administration, DevOps, and security best practices into a cohesive solution.
ποΈ Architecture
Jump Host: The only public SSH entry point.
App Host: Runs Apache, a PHP app, a PostgreSQL database, and Keycloak.
DNS + Letβs Encrypt: Provides valid SSL/TLS certificates.
Firewalld + ipset: Drops all HTTP/HTTPS requests from TOR exit nodes.
π Authentication Flow
User visits
https://demo.example.com
.Apacheβs
mod_auth_openidc
detects that the site is protected and redirects to Keycloak.User authenticates against Keycloak Realm: demo.
Keycloak returns an authorisation code to Apache.
Apache exchanges it for tokens, sets a secure session, and forwards the user back to the PHP app.
This ensures all application access goes through Keycloak authentication.
π‘οΈ Blocking TOR Exit Nodes
TOR exit IPs are downloaded every 30 minutes from torproject.org.
Added to a firewalld
ipset
.Rich rules drop any connection to ports 80/443 from those IPs.
All other traffic is allowed.
This reduces the attack surface by blocking anonymous traffic often used in brute-force or scraping attempts.
βοΈ Step-by-Step Implementation
Task 0 β Prerequisites
Two Linux VMs: Jump Host & App Host.
DNS records:
demo.example.com
β App host IPkeycloak.example.com
β App host IP
SSH keypair for secure login.
Task 1 β OS Hardening
Create a non-root sudo user.
Disable root login & password authentication in
sshd_config
.Enable
auditd
activity logging.Extend shell history with timestamps.
Task 2 β SSH Restriction
Configure firewalld to allow SSH on the App host only from the Jump host IP.
Users must hop through Jump β App.
Task 3 β Application Stack
Install Apache, PHP, and PostgreSQL.
Create database & user:
CREATE USER appuser WITH PASSWORD 'StrongP@ss-ChangeMe'; CREATE DATABASE appdb OWNER appuser;
Deploy a demo PHP app that queries PostgreSQL.
Task 4 β Keycloak Setup
Run Keycloak in Docker with PostgreSQL backend.
Reverse proxy via Apache at
https://keycloak.example.com
.Admin user:
admin / SetAdminPass
.
Task 5 β HTTPS with Letβs Encrypt
Use Certbot with the Apache plugin:
sudo certbot --apache -d demo.example.com -d keycloak.example.com --redirect
Enforce HTTPS and redirect from HTTP β HTTPS.
Task 6 β Keycloak Realm & Client
Create Realm
demo
.Add Client
demo-app
:Redirect URI:
https://demo.example.com/oidc/callback
Web origin:
https://demo.example.com
Copy client secret for Apache config.
Create a test user
testuser
.
Task 7 β Protecting the App with OIDC
Apache vhost config (demo-ssl.conf
):
OIDCProviderMetadataURL https://keycloak.example.com/realms/demo/.well-known/openid-configuration
OIDCClientID demo-app
OIDCClientSecret <client-secret>
OIDCRedirectURI https://demo.example.com/oidc/callback
OIDCCryptoPassphrase SomeRandomString
<Location />
AuthType openid-connect
Require valid-user
</Location>
Now, all requests require Keycloak login.
Task 8 β TOR Blocking with Firewalld
Script: tor-blocklist-refresh.sh
#!/usr/bin/env bash
set -euo pipefail
LOGFILE="/var/log/tor-blocklist.log"
TMP=$(mktemp)
{
echo "[$(date '+%F %T')] Refreshing TOR blocklist..."
curl -s https://check.torproject.org/exit-addresses | \
awk '/^ExitAddress/ {print $2}' | sort -u > "$TMP"
sudo firewall-cmd --permanent --delete-ipset=tor || true
sudo firewall-cmd --permanent --new-ipset=tor --type=hash:ip
while read -r ip; do
sudo firewall-cmd --permanent --ipset=tor --add-entry="$ip"
done < "$TMP"
sudo firewall-cmd --reload
rm -f "$TMP"
echo "[$(date '+%F %T')] Done."
} >> "$LOGFILE" 2>&1
Cron job:
*/30 * * * * root /usr/local/bin/tor-blocklist-refresh.sh
Task 9 β Final Validation
β
Visiting http://demo.example.com
β Redirects to https://demo.example.com
.
β
Redirected to Keycloak β Login β Back to demo app.
β
SSL certificates are valid.
β
SSH to App only works via Jump.
β
TOR traffic dropped.
π Repository
All configs, scripts, and docs are available in the GitHub repo:
π secure-app-stack
secure-app-stack/
βββ README.md
βββ docs/ (setup-guide + diagrams)
βββ scripts/ (tor-blocklist-refresh.sh)
βββ apache/ (vhost configs + demo app)
βββ sql/ (init scripts)
π Conclusion
This project demonstrates how to combine Linux sysadmin, DevOps, and security practices into a single, secure stack:
Enforcing jump-host-only SSH
Using Keycloak for authentication
Enabling valid HTTPS with automatic redirects
Blocking TOR traffic at the firewall
Auditing and hardening the OS
Itβs a real-world-ready blueprint for securing small applications or proving your DevOps skills in interviews.
Subscribe to my newsletter
Read articles from Praneeth Perera directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Praneeth Perera
Praneeth Perera
As a Senior System Administrator at NoeHow, I specialise in network analysis, troubleshooting, automation, and system migrations using Python, Perl, Bash, and MySQL. I'm passionate about training teams and staying current with emerging technologies to optimise system functionality and user experiences. With RHCE and AWS Certified Solutions Architect Associate certifications, a B.Sc. in Information Systems from the University of Colombo, an MBA from the University of Bedfordshire, and a strong accountancy background, I bring technical expertise and business acumen to every project. I thrive in collaborative environments and am eager to connect with innovative professionals.