This guide describes a simple, production-style deployment of this Flask app behind Apache using mod_wsgi.
It aims to be copy/paste friendly and distro-agnostic. Adjust paths and service names for your OS.
- Linux server with Apache installed
- Debian/Ubuntu:
apache2 - RHEL/Fedora/CentOS:
httpd
- Debian/Ubuntu:
mod_wsgiinstalled for the same Python you will run your app with- Prefer the packaged
libapache2-mod-wsgi-py3/mod_wsgiwhere possible. - If you compile/install
mod_wsgiyourself, ensure it targets your Python version.
- Prefer the packaged
- Repo checked out to a stable path, e.g.:
/var/www/help-me-post
- A Python virtualenv created inside the repo:
/var/www/help-me-post/.venv
Example setup:
sudo mkdir -p /var/www/help-me-post
sudo chown -R $USER:$USER /var/www/help-me-post
cd /var/www/help-me-post
# (git clone your fork here)
python3 -m venv .venv
. .venv/bin/activate
pip install -r requirements.txtThis app uses Flask’s instance directory for runtime state by default:
instance/must be writable by the Apache userinstance/uploads/must be writable (uploaded media)instance/*.sqlite3must be writable (SQLite DB file)
Recommended layout:
/var/www/help-me-post/
app/
docs/
tests/
wsgi.py
requirements.txt
instance/
help_me_post.sqlite3
uploads/
Create the directories:
sudo mkdir -p /var/www/help-me-post/instance/uploadsApache/mod_wsgi runs the app under the Apache service user (commonly www-data on Debian/Ubuntu, or apache on RHEL-derived distros). That process needs write access to instance/ so it can:
- create or update the SQLite database
- write uploaded files to
instance/uploads/
Set ownership to your Apache user:
Debian/Ubuntu:
sudo chown -R www-data:www-data /var/www/help-me-post/instance
sudo chmod -R u+rwX,g+rwX,o-rwx /var/www/help-me-post/instanceRHEL/Fedora/CentOS:
sudo chown -R apache:apache /var/www/help-me-post/instance
sudo chmod -R u+rwX,g+rwX,o-rwx /var/www/help-me-post/instanceNotes:
- Keep the rest of the repo readable by Apache, but only
instance/needs to be writable. - If you use SELinux (common on RHEL/Fedora), you may also need to set an appropriate context for write access to
instance/.
The app reads configuration from environment variables (no Docker required). Common ones:
OPENAI_API_KEY(required for real AI calls; if missing, the app returns stub results)OPENAI_MODEL(optional; default is set in the app)SECRET_KEY(recommended in production)DATABASE_PATH(optional; defaults to<instance>/help_me_post.sqlite3)UPLOAD_DIR(optional; defaults to<instance>/uploads)MAX_CONTENT_LENGTH(optional; default is 512MB)
Optional UI customization (landing page):
HMP_AUDIENCE_SUGGESTIONS(optional; comma-separated list or JSON list)HMP_TAG_SUGGESTIONS(optional; comma-separated list or JSON list)HMP_TONE_SUGGESTIONS(optional; comma-separated list or JSON list)HMP_DEFAULT_TEMPLATE_MODE(0/1)HMP_DEFAULT_ADD_EMOJIS(0/1)HMP_DEFAULT_INCLUDE_CTA(0/1)HMP_DEFAULT_CTA_TARGET(optional string)
Important: this repository does not include python-dotenv, so the app will not automatically load a .env file.
- A
.envfile can still be useful for local development (where you export variables in your shell). - For Apache/mod_wsgi, the practical approach is to set environment variables in your Apache VirtualHost using
SetEnv(orPassEnv).
Example SetEnv directives:
# Never commit secrets. Do not put real keys in git.
SetEnv OPENAI_API_KEY "YOUR_KEY_HERE"
SetEnv OPENAI_MODEL "gpt-4.1-mini"
SetEnv SECRET_KEY "a-long-random-secret"
# Optional explicit paths (recommended to avoid ambiguity)
SetEnv DATABASE_PATH "/var/www/help-me-post/instance/help_me_post.sqlite3"
SetEnv UPLOAD_DIR "/var/www/help-me-post/instance/uploads"
# Optional: allow larger uploads (bytes)
SetEnv MAX_CONTENT_LENGTH "536870912"
# Optional UI defaults
SetEnv HMP_DEFAULT_INCLUDE_CTA "0"
SetEnv HMP_DEFAULT_CTA_TARGET ""
SetEnv HMP_TAG_SUGGESTIONS "buildinpublic, indiedev, flask, python, webdev"If your project workflow expects a .env.example, create one that documents the variables above and keep real secrets out of version control. Add .env to .gitignore.
This repo already includes a WSGI entrypoint at wsgi.py.
A minimal wsgi.py should look like:
from app import create_app
app = create_app()Apache/mod_wsgi will import app from this module.
Below is a minimal, non-SSL VirtualHost example. SSL/TLS can be handled separately (e.g., with Let’s Encrypt + a TLS vhost).
Create a site config (path varies by distro):
- Debian/Ubuntu:
/etc/apache2/sites-available/help-me-post.conf - RHEL/Fedora:
/etc/httpd/conf.d/help-me-post.conf
Example configuration:
<VirtualHost *:80>
ServerName help-me-post.example.com
# Point these to your repo + venv.
# Ensure python-home matches the venv created with python3.
WSGIDaemonProcess help-me-post \
python-home=/var/www/help-me-post/.venv \
python-path=/var/www/help-me-post \
processes=2 threads=10
WSGIProcessGroup help-me-post
WSGIScriptAlias / /var/www/help-me-post/wsgi.py
# Environment variables for the app
SetEnv OPENAI_API_KEY "YOUR_KEY_HERE"
SetEnv OPENAI_MODEL "gpt-4.1-mini"
SetEnv SECRET_KEY "a-long-random-secret"
SetEnv DATABASE_PATH "/var/www/help-me-post/instance/help_me_post.sqlite3"
SetEnv UPLOAD_DIR "/var/www/help-me-post/instance/uploads"
# Upload limits: Apache + Flask both matter.
# Apache: limit request body (bytes). Adjust as needed.
LimitRequestBody 536870912
<Directory /var/www/help-me-post>
Require all granted
</Directory>
# Optional: serve static files directly via Apache.
# This is not required (Flask can serve /static), but is common in production.
Alias /static/ /var/www/help-me-post/app/web/static/
<Directory /var/www/help-me-post/app/web/static>
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/help-me-post-error.log
CustomLog ${APACHE_LOG_DIR}/help-me-post-access.log combined
</VirtualHost>Enable and restart (Debian/Ubuntu example):
sudo a2enmod wsgi
sudo a2ensite help-me-post
sudo systemctl reload apache2RHEL/Fedora example:
sudo systemctl restart httpdSymptoms:
- 500 errors
- logs show
OperationalError: unable to open database file - logs show
Permission deniedwriting uploads
Checks:
instance/is writable by the Apache user (www-data/apache)instance/uploads/exists and is writableDATABASE_PATHandUPLOAD_DIRpoint to real paths
Symptoms:
- import errors for packages installed in your venv
- mod_wsgi loads but can’t find Flask or your modules
Checks:
WSGIDaemonProcess ... python-home=/path/to/.venvis correctpython-path=/var/www/help-me-postpoints at the repo root (soimport appworks)- mod_wsgi is installed for Python 3 and compatible with your Apache build
Symptoms:
- AI generation returns stub results unexpectedly
Checks:
SetEnv OPENAI_API_KEYis present and correct- Avoid putting secrets in shell profiles and assuming Apache sees them; Apache runs as a service with its own environment
Symptoms:
- uploads fail with 413 Request Entity Too Large
- uploads hang or are cut off
Places to tune:
- Apache:
LimitRequestBody(in bytes) - Flask:
MAX_CONTENT_LENGTH(in bytes; this app defaults to 512MB)
Also consider:
- Apache timeouts for slow uploads (distro defaults vary)
- Debian/Ubuntu:
/var/log/apache2/help-me-post-error.log
- RHEL/Fedora:
/var/log/httpd/help-me-post-error.log
Start troubleshooting by checking the Apache error log immediately after reproducing a failure.