At a glance
key numbers
4
Phases
5–15 min
Downtime
6
Open Questions
7 days
Rollback window
Data flow
source → destination
Source
conwizamit VPS
→migrate
Destination
Celnet WHM
Phases
four reversible stages
Phase 1
Prep Celnet
Phase 2
Staging + data sync
Phase 3
Cutover
Phase 4
Monitor + decommission
Open decisions
answered before Phase 3
⚪
1 · Final public URL
Stay at stmjournals.celnet.in, or replace WordPress at journals.stmjournals.com?
⚪
2 · Downtime window
5-minute target (needs careful delta tooling) or 15-minute comfort?
⚪
3 · Cutover timing
Which midnight IST? Quiet traffic, Boss asleep → runbook mode.
⚪
4 · cPanel vhost owner
Which existing cPanel user hosts the ProxyPass? Or add a dedicated one?
⚪
5 · articles.stmjournals.com
Moves together? Same app serves both today.
⚪
6 · WordPress retirement
If Q1 = replace WP, when does the WP site come down? 10,100-article 301 map needed.
Phase 1Prep Celnet — zero downtime
0 / 8
▸
Prep Celnet — zero downtime
1.1 Install PostgreSQL 15
# ssh -i ~/.ssh/journals_server -p 522 [email protected]
dnf module reset postgresql -y
dnf module enable postgresql:15 -y
dnf install -y postgresql-server postgresql-contrib libpq-devel
/usr/bin/postgresql-setup --initdb
systemctl enable --now postgresql
systemctl status postgresql --no-pager
1.2 Create role + database
sudo -u postgres psql <<SQL
CREATE ROLE stmnext LOGIN PASSWORD '<generated-strong-password>';
CREATE DATABASE stmnext OWNER stmnext ENCODING 'UTF8'
LC_COLLATE 'en_US.UTF-8' LC_CTYPE 'en_US.UTF-8' TEMPLATE template0;
GRANT ALL PRIVILEGES ON DATABASE stmnext TO stmnext;
SQL
1.3 Create system user
useradd -r -m -d /home/stmnext -s /sbin/nologin stmnext
chmod 750 /home/stmnext
1.4 Clone repo + venv
sudo -u stmnext git clone <repo-url> /home/stmnext/app
cd /home/stmnext/app
sudo -u stmnext git checkout <commit on VPS — currently fe4862e>
sudo -u stmnext /usr/bin/python3.12 -m venv .venv
sudo -u stmnext .venv/bin/pip install --upgrade pip
sudo -u stmnext .venv/bin/pip install -r requirements.txt
1.5 Write env files (secrets rotated)
# /home/stmnext/app/.env.web (mode 600, owner stmnext)
DJANGO_SECRET_KEY=<fresh — do not reuse VPS>
DJANGO_DEBUG=0
DJANGO_ALLOWED_HOSTS=stmjournals.celnet.in,articles.stmjournals.com,articles.stmjournals.in,127.0.0.1,localhost
DJANGO_BEHIND_PROXY=1
EMAIL_HOST=... EMAIL_PORT=... EMAIL_USE_TLS=...
EMAIL_HOST_USER=... EMAIL_HOST_PASSWORD=...
DEFAULT_FROM_EMAIL=... ORDER_NOTIFY_TO=...
# /home/stmnext/app/.env.db (mode 600)
DJANGO_DB_NAME=stmnext DJANGO_DB_USER=stmnext
DJANGO_DB_PASSWORD=<from 1.2>
DJANGO_DB_HOST=127.0.0.1 DJANGO_DB_PORT=5432
1.6 Migrate + collectstatic (empty DB)
sudo -u stmnext bash -c '
cd /home/stmnext/app
set -a; . .env.web; . .env.db; set +a
.venv/bin/python manage.py migrate --noinput
.venv/bin/python manage.py collectstatic --noinput
'
1.7 systemd service
# /etc/systemd/system/stmnext.service
[Unit]
Description=STM Journals (stm-next)
After=network-online.target postgresql.service
Wants=network-online.target
Requires=postgresql.service
[Service]
User=stmnext
Group=stmnext
WorkingDirectory=/home/stmnext/app
EnvironmentFile=/home/stmnext/app/.env.web
EnvironmentFile=/home/stmnext/app/.env.db
ExecStart=/home/stmnext/app/.venv/bin/gunicorn stmnext.wsgi:application \
--bind 127.0.0.1:8099 \
--workers 3 --timeout 120 \
--access-logfile - --error-logfile -
Restart=on-failure
RestartSec=5
PrivateTmp=true
ProtectSystem=full
ProtectHome=read-only
ReadWritePaths=/home/stmnext
[Install]
WantedBy=multi-user.target
ss -tlnp | grep :8099 # must be empty before enabling
systemctl daemon-reload
systemctl enable --now stmnext.service
systemctl status stmnext.service --no-pager
1.8 Loopback smoke test
curl -sI http://127.0.0.1:8099/ | head
curl -s http://127.0.0.1:8099/ | grep -c 'STM Journals'
curl -sI http://127.0.0.1:8099/admin/login/
Phase 2Staging URL + data sync — zero downtime
0 / 4▸
Staging URL + data sync — zero downtime
2.1 Staging subdomain in WHM
Add A record stmnext-staging.celnet.in → 67.225.224.49 (grey-cloud so we hit origin directly).
# /etc/apache2/conf.d/userdata/std/2_4/<cpaneluser>/<site>/stmnext-staging.conf
<IfModule proxy_module>
ProxyPass / http://127.0.0.1:8099/
ProxyPassReverse / http://127.0.0.1:8099/
ProxyPreserveHost On
RequestHeader set X-Forwarded-Proto https
</IfModule>
/usr/local/cpanel/scripts/rebuildhttpdconf
systemctl reload httpd
2.2 Pull Postgres snapshot from VPS
# On VPS:
sudo -u claw bash -c '
cd /home/claw/claudeclaw-home/stm-next
set -a; . .env.web; . .env.db; set +a
PGPASSWORD=$DJANGO_DB_PASSWORD pg_dump -h 127.0.0.1 -U $DJANGO_DB_USER \
$DJANGO_DB_NAME -F c -Z 9 -f /tmp/stmnext.dump
ls -lh /tmp/stmnext.dump
'
# Copy to Celnet, then restore:
scp -i ~/.ssh/journals_server -P 522 \
[email protected]:/tmp/stmnext.dump /tmp/stmnext.dump
sudo -u stmnext bash -c '
cd /home/stmnext/app
set -a; . .env.db; set +a
PGPASSWORD=$DJANGO_DB_PASSWORD pg_restore \
-h 127.0.0.1 -U stmnext -d stmnext --clean --if-exists /tmp/stmnext.dump
'
systemctl restart stmnext.service
2.3 Rsync fulltext PDFs
rsync -av --info=progress2 \
-e 'ssh -i ~/.ssh/journals_server -p 522' \
/home/claw/claudeclaw-home/stm-next/fulltext/ \
[email protected]:/home/stmnext/fulltext/
# On Celnet:
chown -R stmnext:stmnext /home/stmnext/fulltext
2.4 12-URL walk-through
Hit each on https://stmnext-staging.celnet.in/:
Phase 3Cutover — 5-15 min downtime
0 / 6▸
Cutover — 5-15 min downtime
3.1 Pre-cutover
3.2 VPS maintenance mode
# /etc/caddy/Caddyfile — swap stmjournals.celnet.in block:
stmjournals.celnet.in, articles.stmjournals.com {
respond "The site is briefly down for a scheduled migration. Back in ~10 minutes." 503
}
# then: systemctl reload caddy
3.3 Final delta sync
# VPS: fresh dump now that writes are frozen
sudo -u claw pg_dump ... -f /tmp/stmnext-final.dump
scp .../stmnext-final.dump [email protected]:/tmp/
# Celnet:
systemctl stop stmnext.service
sudo -u stmnext pg_restore ... --clean --if-exists /tmp/stmnext-final.dump
rsync -av --delete /home/claw/claudeclaw-home/stm-next/fulltext/ \
-e 'ssh -i ~/.ssh/journals_server -p 522' \
[email protected]:/home/stmnext/fulltext/
systemctl start stmnext.service
3.4 Production Apache vhost on Celnet
# /etc/apache2/conf.d/userdata/std/2_4/<cpaneluser>/<site>/stmnext-prod.conf
<IfModule proxy_module>
ProxyPass / http://127.0.0.1:8099/
ProxyPassReverse / http://127.0.0.1:8099/
ProxyPreserveHost On
RequestHeader set X-Forwarded-Proto https
</IfModule>
# then:
/usr/local/cpanel/scripts/rebuildhttpdconf
systemctl reload httpd
# AutoSSL pre-stage:
/usr/local/cpanel/bin/autossl_check --user=<cpaneluser>
3.5 Cloudflare DNS flip
In Cloudflare dashboard — change proxied A records:
3.6 Live verification
Phase 4Monitor + decommission
0 / 3▸
Monitor + decommission
Days 0–7
Day 7+
# On VPS, after Boss OKs:
systemctl stop stmnext.service
systemctl disable stmnext.service
sudo -u claw pg_dump stmnext | gzip > /home/claw/claw-backups/stmnext-vps-final-$(date +%F).sql.gz
sudo -u claw tar czf /home/claw/claw-backups/stmnext-vps-fulltext-$(date +%F).tgz \
-C /home/claw/claudeclaw-home/stm-next fulltext
# then: remove the stmjournals.celnet.in Caddy block
Rollback
reversible up to Day 7
If Phase 1 fails
Nothing to roll back. VPS untouched. Fix locally and retry.
If Phase 2 fails
systemctl stop stmnext.service on Celnet. Staging DNS stays, no customer impact.
If Phase 3 fails
Cloudflare: flip origin back to 187.127.143.55. VPS Caddyfile: restore original reverse_proxy block. VPS Postgres & fulltext untouched — writes resume as if nothing happened.
Risks
ranked by likelihood × impact
| Risk | Likelihood | Mitigation |
|---|---|---|
| psycopg2 build fails (missing libpq-devel) | medium | Pre-install libpq-devel. Fallback: psycopg[binary] wheel. |
| ACME challenge fails on first AutoSSL pass | medium | Pre-stage with autossl_check --user=<cpaneluser>; cert lands in 5–10 min. |
| Fulltext hardcoded paths (/home/claw/...) | medium | Grep repo pre-cutover; env-var any absolute paths found. |
| Port 8099 already in use on Celnet | low | Check ss -tlnp; shift to 8599 if conflict. |
| Admin PBKDF2 cost mismatch between Django versions | low | Pin requirements.txt; same Django version both sides. |
| CloudLinux LVE applies to system user | low | useradd -r → uid < 1000 → outside LVE. Verify lvectl list. |
| Imunify360 flags gunicorn / Postgres as unknown | low | Whitelist stmnext user; verify no process-kill events. |
| cPanel vhost needs mod_proxy enabled | low | WHM → EasyApache → confirm proxy, proxy_http modules. |
| Googlebot sees 503 during cutover | low | Keep cutover under 15 min; CF cache keeps most pages warm. Monitor Search Console. |
| CF caches old origin stuck | low | Proxied record flip is instant; purge zone cache if any page is stuck. |
| Secrets accidentally reused | low | Rotate DJANGO_SECRET_KEY on Celnet; keep same email/razorpay creds. |
Related
memory + source doc
- Full markdown: ~/Desktop/projects/stm-next-migration/MIGRATION-PLAN.md
- Memory: conwizamit-vps (source), celnet-server-fleet (destination), journals-stmjournals-com-site (what this replaces)
- Prior lessons: celnet-lve-reseller-cap, celnet-opcache-starvation