Successfully building a stable WordPress site on a fresh Debian LXC container and bypassing ISP restrictions to make it public via codedbyte.com. As Homer wisely said, “If it’s too hard to do, then it’s not worth doing.” (We ignore that advice here.)
Infra setup: Proxmox VE Host running a Debian LXC[512MB Memory & 2.0GiB Disk] container running on the below hardware specifications,
AMD Ryzen 5 (4-Core, 3.3 GHz) CPU, 16GB Dual-Channel Memory, and a 512GB NVMe SSD
Part 1: Building the LEMP Stack Foundation
- Initial Setup: Run,
sudo apt update && sudo apt upgrade -y
to update the system. - Install Nginx & DB: Run,
sudo apt install nginx mariadb-server -y
to install the core web server and database. - Install PHP-FPM: Run,
sudo apt install php-fpm php-mysql php-cli php-curl php-gd php-mbstring php-zip -y
Justification: Nginx requires PHP-FPM to handle dynamic WordPress code. - Secure MariaDB: Run,
mysql -u root
Justification: To manually set a strong root password. Critical Security to prevent external database access. - Create WP Database: Create a dedicated database (
wordpressdb) and user (wp_user).
Justification: Security Best Practice (Principle of Least Privilege). - Nginx Configuration and File Deployment
- Download & Move Files: Download WordPress and run,
sudo mv wordpress/* /var/www/html/
to place files in the Nginx web root. - Set Ownership (Fix for 403): Run,
sudo chown -R www-data:www-data /var/www/html
Justification: Fixes the 403 Forbidden error by giving the Nginx process (www-data) read/write access. - Set Permissions: Run permission fixes to ensure Nginx can read the content.
sudo find /var/www/html -type d -exec chmod 755 {} \;
sudo find /var/www/html -type f -exec chmod 644 {} \;
- Download & Move Files: Download WordPress and run,
Part 2: Nginx Server Configuration
- Nginx Server Block Configuration
This file is the core of the web service, configured for fast PHP processing and correct URL handling.
server {
listen 80;
server_name codedbyte.com www.codedbyte.com;
root /var/www/html;
index index.php index.html index.htm;
# Final fix for SSL and Cloudflare redirect issues
proxy_set_header X-Forwarded-Proto $scheme;
# CRITICAL: Adds security headers to prevent clickjacking and other vulnerabilities
add_header X-Frame-Options "SAMEORIGIN";
add_header X-Content-Type-Options "nosniff";
add_header X-XSS-Protection "1; mode=block";
# 1. Main location block: Handles all requests
location / {
# CRITICAL: Ensures Permalinks work by checking for file existence, then passing to index.php
try_files $uri $uri/ /index.php?$args;
}
# 2. PHP processing block
location ~ \.php$ {
# CRITICAL: Point to the correct PHP-FPM socket (e.g., php8.4-fpm.sock)
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
- WordPress
wp-config.phpFixes (The Redirect Override)
This final step eliminates the most persistent issue: the redirect to the internal IP (http://192.168.X.XX/) and the Publishing Failed errors. Edit/var/www/html/wp-config.phpand add this code below the/* That's all, stop editing! */line.
<?php
//Rest of the file
// CRITICAL FIX 1: Overrides database and internal logic, forcing the public domain name
define('WP_HOME','http://codedbyte.com');
define('WP_SITEURL','http://codedbyte.com');
// CRITICAL FIX 2: Cloudflare/Nginx SSL Fix: Forces WordPress to trust the HTTPS connection
// This resolves the 'Publishing failed' API error.
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
$_SERVER['HTTPS'] = 'on';
}
/* That's all, stop editing! */?>
Justification: This code permanently enforces the public URL and ensures the WordPress backend treats the connection as secure, allowing the editor’s API calls to succeed.
Part 3: Going Public with Cloudflare Tunnel
Cloudflare Tunnel and Persistence:
- Install & Authenticate: Install
cloudflaredand run,cloudflared tunnel logincloudflared tunnel create codedbyte-tunnel. - configuring config.yml,
tunnel: codedbyte-tunnel
credentials-file: /etc/cloudflared/<UUID>.json
ingress:
- hostname: codedbyte.com
service: http://localhost:80
- service: http_status:404
- Move Credentials & Config: Copy the generated credentials file (
<UUID>.json) and theconfig.ymlfile into the/etc/cloudflared/directory.cp ~/.cloudflared/* /etc/cloudflared/ - DNS Route: Run,
cloudflared tunnel route dns codedbyte-tunnel codedbyte.com
to create the public CNAME record. - Persistent Service: Configure and start the
systemdservice (cloudflared.service). This runs the tunnel permanently in the background.
CREATING systemd service for persistence:
Create the Service File,sudo nano /etc/systemd/system/cloudflared.service
Service Configuration: This config uses the most stable configuration, relying on the fact that your config and credentials are now correctly located in /etc/cloudflared/
[Unit]
Description=Cloudflare Tunnel Service
After=network.target
[Service]
# Set User=root because LXC often requires root privileges for system-wide access.
User=root
# CRITICAL: This is the only line that needs to be perfectly clean for systemd parsing.
# It explicitly runs the tunnel using the configuration file.
ExecStart=/usr/local/bin/cloudflared tunnel run --config /etc/cloudflared/config.yml codedbyte-tunnel
# Set the working directory to the configuration path to help find credentials
WorkingDirectory=/etc/cloudflared/
Restart=on-failure
RestartSec=5s
# Standard output/error is logged to the systemd journal
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
Enable and Start the Service: Now you instruct the operating system to recognize and start the new service.
- Reload Systemd Daemon: Tell
systemdto look for the new service file.sudo systemctl daemon-reload - Enable Service: Configure the tunnel to start automatically every time the LXC container boots up.
sudo systemctl enable cloudflared - Start Service: Launch the tunnel immediately.
sudo systemctl start cloudflared
Verify Persistence: Check the status to ensure the tunnel is running without errors.
The status must show Active: active (running).
sudo systemctl status cloudflared
Part 4: Troubleshooting Guide: Errors and Fixes
This section documents the specific errors encountered during the setup and the final, successful fixes.
A. Configuration Files to Always Check
- WordPress Core Fix: The final working version of
wp-config.phpmust contain the hardcodedWP_HOMEandWP_SITEURLdefinitions. - Nginx PHP Fix: The
fastcgi_passline must be checked againstls /run/php/to ensure the socket name (e.g.,php8.4-fpm.sock) is correct. - Cloudflare Config: The
config.ymlmust useservice: http://localhost:80and the correct credentials file path.
B. Error Analysis and Solutions
Error Symptom: 403 Forbidden
Root Cause: File system permissions. Nginx user (www-data) could not read the files.
Action to Take: Permission Fix: Set correct ownership: sudo chown -R www-data:www-data /var/www/html.
Error Symptom: 502 Bad Gateway
Root Cause: Nginx connected to a broken or non-existent PHP-FPM socket.
Action to Take: Socket Fix: Verify the active socket using ls /run/php/ and ensure the Nginx fastcgi_pass directive is updated to match.
Error Symptom: Error 1033 (Cloudflare Tunnel error) and URL shows internal IP (http://192.168.X.XX/).
Root Cause: WordPress was redirecting to its hardcoded internal IP.
Action to Take: URL Override Fix: Apply the fixes to wp-config.php (WP_HOME, WP_SITEURL, and the HTTPS check) to eliminate the redirect loop.
Error Symptom: Publishing Failed. You are probably offline.
Root Cause: Cross-protocol (HTTP to HTTPS) API communication failure in the WordPress editor.
Action to Take: SSL Fix: Add the if ($_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') block to wp-config.php to force the backend to trust the Cloudflare connection as secure.
If you’ve made it this far, congratulations! The site is up, the Nginx socket is humming, and the floyvin gloyvins of the configuration are in place.
I approach all feedback with the hyper-analytical enthusiasm of Professor Frink: "Ho-ho! Yes, of course! The whole thing's gone askew! Askew, I tell you! Why didn't I think of that? Why! Why! Why!"
Leave a Reply