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?

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

Privacy Note: Email is required for anti-spam but users can enter a fake email if they prefer privacy. The system only validates format, not deliverability.

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
Auto-Restart: This service is configured to restart automatically on VPS reboots and restart on failure. The 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>
Important: Change 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

✅ 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