Chapter 11: Application Hosting — WordPress, Flask and Express
Common Mistakes I See Students Make
Friends, I see these mistakes in every batch – WordPress without an Elastic IP, wrong ownership, debug=True in production. Read them once.
मित्रांनो, या चुका मी प्रत्येक बॅचमध्ये बघतो – Elastic IP शिवाय WordPress, चुकीची ओनरशिप, प्रॉडक्शनमध्ये debug=True. एकदा वाचून ठेवा.
दोस्तों, ये गलतियाँ मैं हर बैच में देखता हूँ – Elastic IP के बिना WordPress, गलत ओनरशिप, प्रोडक्शन में debug=True. एक बार पढ़ लो.
- Installing WordPress before attaching an Elastic IP — the site breaks after stop/start.
- Wrong ownership of WordPress files (not
www-data/apache) → plugins and uploads fail. - Forgetting
AllowOverride All/a2enmod rewrite→ permalinks give 404. - Skipping the security salts in
wp-config.php. - Running Flask with
app.run(debug=True)in production — debug mode can allow remote code execution. - Wrong module path in Gunicorn (
app:appvswsgi:app) → ModuleNotFoundError injournalctl. - Not running the command printed by
pm2 startup, so apps don't come back after reboot.
Each mistake, what you see on screen, and the fix
These are the problems I see most often when students host WordPress, Flask and Express on EC2. For each one: what breaks, what you see, and how to fix it.
1. WordPress installed before attaching an Elastic IP
WordPress saves the site address in its database (siteurl and home). A normal EC2 public IP changes when you stop and start the instance. After a restart, the site redirects to the old IP and looks broken.
What you see: the browser spins, then tries to open the old IP address.
Fix:
- Allocate an Elastic IP and associate it with the instance.
- Update the two values, using WP-CLI if it is installed:
wp option update home 'http://NEW_IP'andwp option update siteurl 'http://NEW_IP'. - Better still, use a domain name from day one.
2. Wrong file ownership
The web server user must be able to write to wp-content. On Ubuntu this user is www-data. On Amazon Linux with Apache it is apache.
What you see: "Installation failed: Could not create directory" when adding a plugin.
Fix (Ubuntu): sudo chown -R www-data:www-data /var/www/html/wp-content. Never use chmod 777. It lets anyone on the server change your files.
3. Permalinks give 404
Pretty URLs like /blog/my-post/ need Apache's rewrite module and permission to read .htaccess.
Fix (Ubuntu): sudo a2enmod rewrite, set AllowOverride All for the site directory, then sudo systemctl restart apache2.
4. Skipping the security salts
wp-config.php has keys and salts that protect login cookies. Leaving the sample placeholder values in place weakens them. Generate fresh values from the official WordPress salt generator, paste them in, and keep the file out of any public Git repo.
5. Flask with debug=True in production
Flask's debugger shows an interactive console in the browser when an error happens. If it is reachable from the internet, an attacker may be able to run code on your server. This is a real, documented risk.
Global example: the Flask documentation itself warns never to use the debugger on a production server.
Fix: run the app with Gunicorn behind Nginx, with debug off.
6. Wrong Gunicorn module path
If your file is app.py and the Flask object is named app, the target is app:app. If it lives in wsgi.py, the target is wsgi:app.
What you see: sudo journalctl -u myapp shows ModuleNotFoundError, and Nginx returns 502 Bad Gateway.
7. PM2 apps do not come back after reboot
pm2 startup prints a command that you must copy and run with sudo. Then run pm2 save. Without both steps, your Express app stays down after a restart.
Indian-company scenario
Suppose we are a junior cloud engineer at a Bengaluru startup hosting a Flask API for a food-delivery partner portal. This is sample data. After a weekend reboot, monitoring shows:
curl -I https://api.example.in → HTTP/1.1 502 Bad Gateway
Checklist: Is Gunicorn running? (systemctl status) Is it enabled at boot? (systemctl enable) Is the socket path in Nginx the same as Gunicorn's? Two minutes with this checklist beats an hour of guessing.
Everyday analogy
A restaurant (Nginx) takes your order and passes it to the kitchen (Gunicorn/PHP-FPM/PM2). If the kitchen is closed, the waiter can only say "sorry". That is the 502 error. The front door is open, but nobody is cooking.
The same mistakes in other stacks
The three apps in this chapter are WordPress (PHP), Flask (Python) and Express (Node.js). The same kinds of mistakes appear in other stacks too.
Java / Spring Boot on Tomcat or as a JAR
Mistake: running the app as root on port 80. Fix: run as a normal user on 8080 behind nginx, using a systemd service.
[Service]
User=appuser
ExecStart=/usr/bin/java -jar /opt/app/app.jarWhat you see:
systemctl status app -> active (running)C# / ASP.NET Core
Mistake: ASPNETCORE_ENVIRONMENT=Development in production, which shows detailed error pages. Fix: set Production.
Environment=ASPNETCORE_ENVIRONMENT=ProductionWhat you see:
Errors show a generic page, not a stack tracePython / Django
Mistake: DEBUG=True and an empty ALLOWED_HOSTS. Fix: DEBUG off and list your domain.
ALLOWED_HOSTS = ['example.in']What you see:
Invalid HTTP_HOST header -> 400 for unknown hostsGo
Mistake: building on a laptop with a different OS or CPU. Fix: cross-compile with the right flags.
GOOS=linux GOARCH=amd64 go build -o appWhat you see:
exec format error (what you see when you skip this)PHP / Laravel
Mistake: the web root points to the project folder instead of public/, which can expose .env. Fix: set root to public.
root /var/www/app/public;What you see:
GET /.env -> 404Ruby / Rails
Mistake: forgetting to precompile assets, so CSS is missing. Fix: precompile during deploy.
RAILS_ENV=production bin/rails assets:precompileWhat you see:
Writing public/assets/application-*.cssnginx in front of any app
Mistake: proxy_pass to the wrong port. Fix: match it to the app's port (8080, 3000, 5000...).
proxy_pass http://127.0.0.1:8000;What you see:
502 Bad Gateway when wrongDatabases: MySQL, PostgreSQL, MongoDB, Redis
Mistake: an app config pointing to localhost after moving to RDS, Atlas or ElastiCache. Fix: use the managed endpoint stored in an environment variable or a secrets manager.
What you see:
Connection refused 127.0.0.1:3306 (before the fix)AWS / Azure / GCP managed hosting
Elastic Beanstalk, Azure App Service and Google App Engine or Cloud Run take care of the process manager for you. The usual mistake is a wrong start command or port. Read the platform logs first.
What you see:
Container failed to start and listen on the port defined by PORT=8080Summary
| Stack | Classic mistake | Fix |
|---|---|---|
| Spring Boot | Run as root on :80 | User service on :8080 + nginx |
| ASP.NET Core | Development env in prod | Production env |
| Django | DEBUG True | DEBUG False + ALLOWED_HOSTS |
| Go | Wrong OS/arch build | GOOS/GOARCH |
| Laravel | Root not public/ | root .../public |
| Rails | No asset precompile | assets:precompile |
| nginx | Wrong proxy port | Match app port |
| Databases | localhost after migration | Managed endpoint via env |
Recap
- Elastic IP or a domain before WordPress. Correct owner, never 777.
- Turn on rewrite for permalinks, and set fresh salts.
- No Flask debug in production. Check the Gunicorn module path.
- PM2: run the startup command, then
pm2 save.