This guide explains how to configure Apache2 to serve Pro Mode generated files (charts, CSVs, etc.) in production.
Pro Mode generates files (charts, data exports) that need to be accessible via the web. These files:
- Are generated by the backend and saved to
PROMODE_PUBLIC_DIR - Must be served by Apache at the URL path
/static/promode/ - Should be web-accessible but stored outside the main application directory
/home/hanlulong/openecon-data/
├── backend/ # FastAPI application
├── packages/frontend/dist/ # Built frontend (served by Apache)
└── public_media/
└── promode/ # Pro Mode generated files
├── promode_abc123_chart.png
└── promode_xyz789_data.csv
/tmp/
└── promode_sessions/ # Session storage (not web-accessible)
└── <session-id>/
└── *.pkl
Add these to your production .env file:
# Pro Mode Configuration
PROMODE_PUBLIC_DIR=/home/hanlulong/openecon-data/public_media/promode
PROMODE_SESSION_DIR=/tmp/promode_sessionsImportant:
PROMODE_PUBLIC_DIRmust be a directory that Apache can readPROMODE_SESSION_DIRdoes NOT need to be web-accessible (it's for backend storage only)
Add this to your Apache virtual host configuration (e.g., /etc/apache2/sites-available/openecon.ai-le-ssl.conf):
<VirtualHost *:443>
ServerName openecon.ai
# Serve built frontend
DocumentRoot /home/hanlulong/openecon-data/packages/frontend/dist
# Serve Pro Mode generated files at /static/promode/
Alias /static/promode /home/hanlulong/openecon-data/public_media/promode
<Directory /home/hanlulong/openecon-data/public_media/promode>
Require all granted
Options -Indexes +FollowSymLinks
AllowOverride None
# CORS headers for images/data files
<FilesMatch "\.(png|jpg|jpeg|gif|svg|csv|json|txt)$">
Header set Access-Control-Allow-Origin "*"
Header set Access-Control-Allow-Methods "GET, OPTIONS"
</FilesMatch>
</Directory>
# Proxy API requests to backend
ProxyPreserveHost On
ProxyPass /api http://localhost:3001/api
ProxyPassReverse /api http://localhost:3001/api
# Frontend fallback (SPA routing)
<Directory /home/hanlulong/openecon-data/packages/frontend/dist>
Require all granted
Options -Indexes +FollowSymLinks
AllowOverride None
# Handle SPA routing
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</Directory>
# SSL Configuration (managed by Certbot)
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/openecon.ai/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/openecon.ai/privkey.pem
</VirtualHost>mkdir -p /home/hanlulong/openecon-data/public_media/promode
chmod 755 /home/hanlulong/openecon-data/public_media
chmod 755 /home/hanlulong/openecon-data/public_media/promode# Test config syntax
sudo apache2ctl configtest
# If OK, reload Apache
sudo systemctl reload apache2Create a test file:
echo "Test file" > /home/hanlulong/openecon-data/public_media/promode/test.txtAccess it in browser:
https://openecon.ai/static/promode/test.txt
You should see "Test file" displayed.
- Log in to openecon-data
- Enable Pro Mode
- Run code that generates a chart:
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 10, 100)
y = np.sin(x)
plt.figure(figsize=(10, 6))
plt.plot(x, y)
plt.title('Test Chart')
plt.xlabel('X')
plt.ylabel('Y')
plt.savefig('/tmp/promode_test_chart.png', bbox_inches='tight', dpi=100)
print('Chart saved to /tmp/promode_test_chart.png')
plt.show()The chart should display in the chat interface.
Cause: Apache alias not configured correctly.
Fix:
# Check Apache config
sudo apache2ctl -S | grep openecon
# Verify Alias directive exists
sudo grep -n "Alias /static/promode" /etc/apache2/sites-available/openecon.ai-le-ssl.conf
# Reload Apache
sudo systemctl reload apache2Cause: Directory permissions or Apache configuration.
Fix:
# Fix permissions
chmod 755 /home/hanlulong/openecon-data/public_media
chmod 755 /home/hanlulong/openecon-data/public_media/promode
chmod 644 /home/hanlulong/openecon-data/public_media/promode/*.png
chmod 644 /home/hanlulong/openecon-data/public_media/promode/*.csv
# Check Apache error log
sudo tail -50 /var/log/apache2/openecon-error.logCause: Backend not running or PROMODE_PUBLIC_DIR misconfigured.
Fix:
# Check backend logs
tail -50 /tmp/backend-production.log | grep -i promode
# Verify environment variable is set
cd /home/hanlulong/openecon-data
source backend/.venv/bin/activate
python3 -c "from backend.config import get_settings; print(f'PROMODE_PUBLIC_DIR: {get_settings().promode_public_dir}')"
# Restart backend (recommended: python3 scripts/restart_dev.py --backend)
killall -9 uvicorn
nohup uvicorn backend.main:app --host 0.0.0.0 --port 3001 --reload > /tmp/backend-production.log 2>&1 &Cause: Cleanup not running.
Fix: Files older than 24 hours are automatically cleaned up by the backend. To manually clean:
# Remove old files
find /home/hanlulong/openecon-data/public_media/promode -type f -mtime +1 -delete
find /tmp/promode_sessions -type d -mtime +1 -exec rm -rf {} +When running locally, no Apache configuration is needed:
- Frontend dev server (Vite) runs on port 5173
- Backend serves files directly or via dev proxy
- Defaults work out of the box:
PROMODE_PUBLIC_DIR:{project_root}/public_media/promodePROMODE_SESSION_DIR:{system_temp}/promode_sessions
Use the Apache configuration shown above.
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="Pro Mode Static Files">
<match url="^static/promode/(.*)" />
<action type="Rewrite" url="C:\openecon-data\public_media\promode\{R:1}" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>server {
listen 443 ssl;
server_name openecon.ai;
# Serve frontend
root /home/hanlulong/openecon-data/packages/frontend/dist;
# Serve Pro Mode files
location /static/promode/ {
alias /home/hanlulong/openecon-data/public_media/promode/;
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods "GET, OPTIONS";
}
# Proxy API
location /api/ {
proxy_pass http://localhost:3001/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# SPA fallback
location / {
try_files $uri $uri/ /index.html;
}
}- Directory Permissions: Ensure files are readable but not writable by Apache
- File Types: Only allow safe file types (PNG, CSV, JSON)
- File Cleanup: Old files are automatically cleaned up after 24 hours
- CORS: Configured to allow file access from your domain only
- No Directory Listing:
-Indexesprevents browsing the directory