I wanted a comment system for my blog posts and book reviews, but I didn't want to use third-party services like Disqus (privacy concerns, ads, tracking) or rely on external databases. Here's how I built a simple, secure, self-hosted comment system using Python and Nginx.
The Architecture
┌─────────────────────────────────────────────────────────────────┐
│ USER'S BROWSER │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Blog Post / Book Review │ │
│ │ - Comment form (HTML) │ │
│ │ - JavaScript (fetches/posts comments) │ │
│ └─────────────────────────────────────────────────────────┘ │
└───────────────────────────┬─────────────────────────────────────┘
│ HTTP requests
▼
┌─────────────────────────────────────────────────────────────────┐
│ NGINX (magic user) │
│ - Serves static files │
│ - Proxies /blog/api/* → 127.0.0.1:11435 │
└───────────────────────────┬─────────────────────────────────────┘
│ Reverse proxy
▼
┌─────────────────────────────────────────────────────────────────┐
│ PYTHON BACKEND (user user) │
│ - Port 11435 │
│ - HTTPServer (no framework needed) │
│ - Handles POST /blog/api/comment │
│ - Handles GET /blog/api/comments/<type>/<id> │
│ - Rate limiting (3 comments / 5 min per IP) │
│ - XSS protection (HTML escaping) │
│ - Email format validation │
└───────────────────────────┬─────────────────────────────────────┘
│ Reads/Writes
▼
┌─────────────────────────────────────────────────────────────────┐
│ JSON DATA FILES │
│ /home/user/blog-comments/data/comments.json (chmod 600) │
│ /home/user/blog-comments/data/highscores.json (chmod 600) │
└─────────────────────────────────────────────────────────────────┘
Why This Design?
- Self-hosted: No external dependencies, no third-party tracking
- Simple: Python HTTPServer with JSON files (no database to maintain)
- Secure: XSS protection, rate limiting, private data storage
- Flexible: Works for blogs, book reviews, and future games
- Lightweight: ~9MB RAM, minimal CPU usage
File Structure
/home/user/blog-comments/
├── backend/
│ └── main.py # Python HTTP server (port 11435)
├── data/
│ ├── comments.json # All comments (chmod 600)
│ └── highscores.json # Future game high scores (chmod 600)
├── snippets/
│ ├── blog-comments.html # HTML/JS for blog posts
│ └── book-comments.html # HTML/JS for book reviews
├── comment-system.service # Systemd service file
├── nginx-config.txt # Nginx proxy config
└── README.md # Documentation
The Backend (Python)
The backend is a simple Python HTTP server that handles two endpoints:
POST /blog/api/comment
Submits a new comment. Required fields: name, email, content_type, content_id, comment.
GET /blog/api/comments/<type>/<id>
Retrieves all comments for a specific piece of content.
Security Features
- XSS Protection: All user input is HTML-escaped before storage and display
- Rate Limiting: Max 3 comments per 5 minutes per IP address
- Email Validation: Basic format check (needs @ and domain)
- Private Storage: Data files are chmod 600 (only owner can read)
- Length Limits: Comments max 2000 chars, names/emails max 100 chars
Setting Up the Service
The backend runs as a system-wide systemd service with auto-restart on VPS reboot. Here's the complete setup:
1. Create the Systemd Service
Create the service file at /etc/systemd/system/blog-comments-backend.service:
[Unit]
Description=Blog Comments Backend Server
After=network.target
[Service]
Type=simple
User=user
WorkingDirectory=/home/user/blog-comments/backend
Environment="TELEGRAM_BOT_TOKEN=your_bot_token"
Environment="TELEGRAM_CHAT_ID=your_chat_id"
Environment="GOG_ACCOUNT=your_email@gmail.com"
Environment="GOG_KEYRING_BACKEND=file"
Environment="GOG_KEYRING_PASSWORD=your_keyring_pass"
ExecStart=/usr/bin/python3 /home/user/blog-comments/backend/main.py
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Then enable and start the service:
# Copy service file to systemd directory
sudo cp blog-comments-backend.service /etc/systemd/system/
# Reload systemd configuration
sudo systemctl daemon-reload
# Start the service
sudo systemctl start blog-comments-backend
# Enable auto-start on boot
sudo systemctl enable blog-comments-backend
# Check status
sudo systemctl status blog-comments-backend --no-pager
Restart=always and WantedBy=multi-user.target settings ensure it starts with the system.
2. View Logs
# Follow logs in real-time
sudo journalctl -u blog-comments-backend -f
# View recent logs
sudo journalctl -u blog-comments-backend -n 50
# View logs since today
sudo journalctl -u blog-comments-backend --since today
3. Restart After Changes
sudo systemctl restart blog-comments-backend
4. Troubleshooting Service Issues
# Check if service is enabled (starts on boot)
sudo systemctl is-enabled blog-comments-backend
# Check if service is currently running
sudo systemctl is-active blog-comments-backend
# Check for port conflicts
sudo ss -tlnp | grep 11435
# View full service status
sudo systemctl status blog-comments-backend
Nginx Configuration
The Nginx config on the magic user proxies requests to the Python backend:
# Add to server block in /etc/nginx/sites-available/magic-ian-metal.com
location /blog/api/ {
proxy_pass http://127.0.0.1:11435;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
Then reload Nginx:
sudo nginx -t && sudo nginx -s reload
Adding Comments to a Blog Post
Insert this HTML before </body> in your blog post:
<!-- Comments Section -->
<section class="comments-section" id="comments">
<h3>💬 Comments</h3>
<div id="comment-status"></div>
<form id="comment-form">
<input type="text" id="comment-name" placeholder="Your name" required>
<input type="email" id="comment-email" placeholder="Your email" required>
<textarea id="comment-text" placeholder="Share your thoughts..." required></textarea>
<button type="submit" id="submit-btn">Post Comment</button>
</form>
<div id="comments-list">Loading comments...</div>
</section>
CONTENT_ID in the JavaScript to match your blog post folder name.
Adding Comments to Book Reviews
Book reviews use scroll-snap slides, so the comments must be added inside the slides container as a final slide. See the book-review-slides skill for the template.
Troubleshooting
Service won't start
# Check logs
journalctl --user -u comment-system -n 20
# Check if port is in use
ss -tlnp | grep 11435
Comments not loading
# Check if backend is running
curl http://127.0.0.1:11435/blog/api/comments/blog/test-post
# Check browser console for errors (F12)
Future Improvements
Email notifications for new subscribers✅ ImplementedSystemd auto-restart on VPS boot✅ Implemented- Add comment moderation (admin panel)
- Email notifications for replies to comments
- Gravatar support for avatars
- Migrate to SQLite for better querying
- Add CAPTCHA for bot prevention
✅ Summary
- Self-hosted comment system with Python backend
- JSON storage with proper permissions
- XSS protection and rate limiting
- Nginx reverse proxy for public access
- Systemd service with auto-restart on VPS boot
- Email confirmation and Telegram notifications for subscribers
- Deployed to all blog posts and book reviews
💬 Comments