Setting Up Separate Development and Production XenForo 2.3 Websites on the Same VPS
Introduction
This tutorial explains how to maintain two separate XenForo installations on a single VPS:
- Production: https://mydomain.com — the live forum, accessible to members.
- Development: https://dev.mydomain.com — a private testing environment used to develop new features, test upgrades, install add-ons, and experiment with the design.
The objective is to test changes safely before applying them to the production website, without losing live member registrations, discussions, private messages, or attachments.
The tutorial also explains how to refresh the development environment from production when realistic test data is required.
It is designed for XenForo 2.3 running on Linux with Nginx, PHP-FPM and MariaDB. The examples use a Centmin Mod-style directory structure, but the concepts apply to other VPS configurations.
Important: This is a controlled deployment workflow, not automatic two-way database synchronization.
1. Understanding the Two Different Workflows
There are two distinct operations:
A. Production → Development: Refreshing the test website
This involves copying the live database and website files to development, allowing you to test against a realistic copy of the live forum.
It can overwrite existing development data.
B. Development → Production: Deploying tested changes
This involves selectively transferring tested updates, custom code, add-ons and configurations to the live website.
The development database must not replace the production database after the site has launched.
Otherwise, any new members, posts and messages created on the live website since the databases diverged could be lost.
The two workflows require different procedures.
2. Recommended Server Structure
Both XenForo installations can reside on the same VPS:
VPS
│
├── Nginx
├── PHP-FPM
├── MariaDB
│ ├── mydomain_prod
│ └── mydomain_dev
│
├── /home/nginx/domains/mydomain.com/
│ └── public/ Production XenForo
│
└── /home/nginx/domains/dev.mydomain.com/
└── public/ Development XenForo
Each installation must have:
| Component | Production | Development |
|---|
| Domain | mydomain.com | dev.mydomain.com |
| Document root | Separate | Separate |
| Database | mydomain_prod | mydomain_dev |
| DB user | mydomain_prod | mydomain_dev |
| src/config.php | Separate | Separate |
| Cookies | Separate prefix | Separate prefix |
| Uploaded files | Separate | Separate |
| Email | Enabled when configured | Disabled |
| Access | Public | Restricted |
| Cache and search indexes | Production-specific | Development-specific |
This provides logical separation, but both installations still share the VPS operating system and resources. It does not provide the same isolation as two separate servers.
3. Prepare the Two Websites
Create two Nginx virtual hosts:
mydomain.com
dev.mydomain.com
Configure DNS records, HTTPS certificates and PHP-FPM for both websites.
For example, the document roots could be:
/home/nginx/domains/mydomain.com/public
/home/nginx/domains/dev.mydomain.com/public
Use valid HTTPS certificates and test both virtual hosts separately.
If using Cloudflare, enable Full (Strict) TLS and configure appropriate origin security.
Protect the development website before uploading any private or sensitive production data.
Secure the development website with Cloudflare Access
For a Cloudflare-proxied development hostname, create a self-hosted Cloudflare Access application for:
dev.mydomain.com
Create an Allow policy for only the authorized developers or administrators.
Google authentication, one-time PINs or another supported identity provider can be used.
Test access in a private browser window:
- An approved account should successfully authenticate.
- An unauthorized account should be denied.
- An unauthenticated visitor should not reach the development site.
If using Nginx rather than Apache, do not depend on .htaccess for access control. Nginx does not process .htaccess files.
Keep the production and development Cloudflare security rules clearly scoped by hostname. A broad IP-based Zone Lockdown rule can unintentionally block development administrator URLs.
4. Configure Nginx for XenForo
Each XenForo virtual host should have appropriate routing and private-directory restrictions.
Inside the relevant HTTPS server block, use the following as a starting point:
# Prevent direct access to private directories
location ^~ /src/ {
deny all;
}
location ^~ /internal_data/ {
deny all;
}
location ^~ /library/ {
deny all;
}
# XenForo friendly URL routing
location / {
try_files $uri $uri/ /index.php?$uri&$args;
}
These rules must be integrated with the existing PHP-FPM handler and any included Nginx security configuration, rather than pasted as a complete replacement for the virtual host.
Do not block /data/ entirely. XenForo uses that directory for public assets such as avatars and attachment thumbnails.
For additional hardening, restrict executable PHP files to the entry points actually required by XenForo and installed add-ons. Also protect configuration backups, temporary archives and database dumps.
Do not leave complete XenForo installation ZIPs or backup files in publicly accessible directories.
After editing Nginx:
nginx -t
systemctl reload nginx
Confirm that the configuration test succeeds before reloading.
5. Create Separate Databases
Connect to MariaDB using an administrative account:
mariadb
Create production and development databases:
CREATE DATABASE mydomain_prod
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
CREATE DATABASE mydomain_dev
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
Create separate database accounts:
CREATE USER 'mydomain_prod'@'localhost'
IDENTIFIED BY 'REPLACE_WITH_UNIQUE_PRODUCTION_PASSWORD';
CREATE USER 'mydomain_dev'@'localhost'
IDENTIFIED BY 'REPLACE_WITH_UNIQUE_DEVELOPMENT_PASSWORD';
GRANT ALL PRIVILEGES ON mydomain_prod.*
TO 'mydomain_prod'@'localhost';
GRANT ALL PRIVILEGES ON mydomain_dev.*
TO 'mydomain_dev'@'localhost';
Use unique, randomly generated passwords and keep them out of shell history, screenshots and source control.
For each installation, its src/config.php must reference only its own database.
Never configure the development site to connect to the production database.
6. Install XenForo
Download the official XenForo installation package from the XenForo Customer Area.
Extract the archive and copy the
contents of its upload directory into the respective website document root.
For example:
rsync -a /path/to/xenforo/upload/ \
/home/nginx/domains/dev.mydomain.com/public/
Run the XenForo installer at:
Provide the development database credentials.
Use the appropriate ownership and directory permissions for the PHP-FPM worker account. Avoid unnecessarily making files writable by every system user.
For production, deploy the same tested XenForo release to its own document root and connect it to mydomain_prod.
There is no need to install production immediately while development is still in progress.
7. Development-Specific Configuration
Edit:
/home/nginx/domains/dev.mydomain.com/public/src/config.php
Retain the development database connection details and add:
// Development environment only
$config['enableMail'] = false;
$config['cookie']['prefix'] = 'mydomain_dev_';
Disabling mail prevents XenForo from sending messages to actual members when the development database contains copied production data.
The separate cookie prefix reduces interference between logins on the two installations.
Keep the cookie domain at its default host-only setting unless there is a specific reason to change it.
Consider disabling real payment processing on development:
$config['enableLivePayments'] = false;
Also review any active third-party email clients, webhooks, payment integrations, OAuth applications, external storage services and background tasks. XenForo's enableMail setting does not automatically disable those external systems.
For Redis or another shared cache, configure separate namespaces, database numbers or instances where supported. If using XenForo Enhanced Search, configure separate indexes.
You should also give the development installation a distinct title such as:
MyDomain — Development
Set its Board URL to:
Keep the licensed production URL configured as:
8. Keep Production and Development Backups Separate
Before making changes to either installation, take independent backups.
A complete XenForo backup should account for:
- The MariaDB database.
- The application files and custom add-ons.
- The data directory.
- The internal_data directory, including attachments.
- Configuration files and relevant Nginx settings.
Do not assume a database backup contains attachments.
A VPS snapshot is useful for disaster recovery, but it should not be the only backup method.
Prefer keeping encrypted backup copies outside the VPS and periodically testing restoration.
Part II — Refresh Development from Production
9. Before You Begin
The following procedure deliberately replaces the development environment with a copy of production.
Do not run it if there are development-only changes you still need to preserve.
First:
- Back up the existing development database and files.
- Commit or export custom development code and add-on development output.
- Record development-specific settings and secrets.
- Identify any external integrations that must remain disabled.
- Confirm that production is healthy and that sufficient disk space is available.
For small forums, a transaction-consistent MariaDB dump and a file copy may be sufficient. For larger, actively changing forums, coordinate database and attachment snapshots or use a consistent backup window so the database and files represent compatible states.
10. Export the Production Database
On the VPS:
mkdir -p /root/xf-refresh
chmod 700 /root/xf-refresh
Export the database:
mariadb-dump --single-transaction \
--quick --routines --triggers \
-u mydomain_prod -p \
mydomain_prod > /root/xf-refresh/prod.sql
The command prompts for the production database password instead of placing it in the command line.
Check that the dump completed successfully and has a plausible file size.
For a fully consistent backup with attachments, coordinate any active writes before the final snapshot of data and internal_data.
Do not store the database dump inside either website's public directory.
11. Copy Production Files to a Staging Directory
Avoid directly copying files into the active development installation before preserving the settings that are unique to it.
Create a protected staging directory:
mkdir -p /root/xf-refresh/site
chmod 700 /root/xf-refresh/site
Copy production files:
rsync -a \
/home/nginx/domains/mydomain.com/public/ \
/root/xf-refresh/site/
The trailing slash ensures hidden files are included.
This copy is the starting point for the new development environment.
Before promoting it, replace its production-specific configuration with the saved development configuration and verify that the application will not connect to live services.