{"id":35012,"date":"2025-12-20T16:33:07","date_gmt":"2025-12-20T09:33:07","guid":{"rendered":"https:\/\/dps.media\/?p=35012"},"modified":"2025-12-20T16:33:09","modified_gmt":"2025-12-20T09:33:09","slug":"huong-dan-migrate-n8n-sang-vps-moi-docker-day-du-an-toan-2","status":"publish","type":"post","link":"https:\/\/dps.media\/en\/huong-dan-migrate-n8n-sang-vps-moi-docker-day-du-an-toan-2\/","title":{"rendered":"Guide to migrate n8n to a new VPS (Docker) \u2013 Complete &amp; Safe"},"content":{"rendered":"<p class=\"wp-block-paragraph\">Migrating n8n from old VPS to new VPS sounds simple, but in reality, if you miss a few important steps, you may face serious errors such as losing credentials, webhook not working, or even the entire workflow disappearing.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This article is written based on real-world experience when migrating n8n in a production environment, ensuring the safest and most complete process.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"683\" src=\"https:\/\/dps.media\/wp-content\/uploads\/2025\/12\/image-7-1024x683.png\" alt=\"\" class=\"wp-image-35013\" title=\"\" srcset=\"https:\/\/dps.media\/wp-content\/uploads\/2025\/12\/image-7-1024x683.png 1024w, https:\/\/dps.media\/wp-content\/uploads\/2025\/12\/image-7-300x200.png 300w, https:\/\/dps.media\/wp-content\/uploads\/2025\/12\/image-7-768x512.png 768w, https:\/\/dps.media\/wp-content\/uploads\/2025\/12\/image-7-18x12.png 18w, https:\/\/dps.media\/wp-content\/uploads\/2025\/12\/image-7-754x503.png 754w, https:\/\/dps.media\/wp-content\/uploads\/2025\/12\/image-7-356x237.png 356w, https:\/\/dps.media\/wp-content\/uploads\/2025\/12\/image-7.png 1536w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption><\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Objective of the migration process<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">After completing the migration, the new VPS must ensure:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Normal n8n login<\/li>\n\n\n\n<li>All workflows remain intact<\/li>\n\n\n\n<li>Credentials are not faulty (no red color displayed)<\/li>\n\n\n\n<li>Webhooks and Cron operate stably<\/li>\n\n\n\n<li>Execution history is preserved<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Components to be migrated (VERY IMPORTANT)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A complete n8n stack includes 4 mandatory parts:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">1. Database<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>PostgreSQL \/ MySQL \/ SQLite<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">2. n8n data directory<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Workflows<\/li>\n\n\n\n<li>Credentials<\/li>\n\n\n\n<li>Executions<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">3. Environment variables<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Especially <code data-no-translation=\"\">N8N_ENCRYPTION_KEY<\/code><\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">4. Domain &amp; webhook configuration<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Note:<\/strong> Missing even one of these four parts can cause serious errors in the entire system.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Step 1: Prepare on old VPS (Source VPS)<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">1.1. Check if n8n is running<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">\u0110\u1ea7u ti\u00ean, ki\u1ec3m tra xem n8n \u0111ang ch\u1ea1y nh\u01b0 th\u1ebf n\u00e0o:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">bash<\/p>\n\n\n\n<pre class=\"wp-block-code\" data-no-translation=\"\"><code data-no-translation=\"\">docker ps\ndocker compose ps<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">X\u00e1c \u0111\u1ecbnh c\u00e1c th\u00f4ng tin sau:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Type of database in use<\/li>\n\n\n\n<li>Volume \u0111ang mount \u1edf \u0111\u00e2u (<code data-no-translation=\"\">~\/.n8n<\/code> or Docker volume)<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">1.2. Backup database<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>With PostgreSQL:<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">bash<\/p>\n\n\n\n<pre class=\"wp-block-code\" data-no-translation=\"\"><code data-no-translation=\"\">docker exec -t postgres \\\n  pg_dump -U n8n n8n &gt; n8n.sql<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>With MySQL:<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">bash<\/p>\n\n\n\n<pre class=\"wp-block-code\" data-no-translation=\"\"><code data-no-translation=\"\">docker exec -t mysql \\\n  mysqldump -u n8n -p n8n &gt; n8n.sql<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>With SQLite:<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">bash<\/p>\n\n\n\n<pre class=\"wp-block-code\" data-no-translation=\"\"><code data-no-translation=\"\">cp ~\/.n8n\/database.sqlite n8n.sqlite<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">1.3. Backup n8n data<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>If using bind mount:<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">bash<\/p>\n\n\n\n<pre class=\"wp-block-code\" data-no-translation=\"\"><code data-no-translation=\"\">tar -czvf n8n-data.tar.gz ~\/.n8n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>If using Docker volume:<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">bash<\/p>\n\n\n\n<pre class=\"wp-block-code\" data-no-translation=\"\"><code data-no-translation=\"\">docker run --rm \\\n  -v n8n_data:\/data \\\n  -v $(pwd):\/backup \\\n  alpine tar czvf \/backup\/n8n-data.tar.gz \/data<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Important notes:<\/strong> This directory contains all encrypted credentials.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">1.4. Save environment variables (.env)<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">File <code data-no-translation=\"\">.env<\/code> must have the following variables:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">env<\/p>\n\n\n\n<pre class=\"wp-block-code\" data-no-translation=\"\"><code data-no-translation=\"\">N8N_ENCRYPTION_KEY=\nDB_TYPE=\nDB_POSTGRESDB_DATABASE=\nDB_POSTGRESDB_USER=\nDB_POSTGRESDB_PASSWORD=\nN8N_HOST=\nWEBHOOK_URL=<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>\u26a0\ufe0f WARNING:<\/strong> Loss <code data-no-translation=\"\">N8N_ENCRYPTION_KEY<\/code> means losing all credentials!<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Step 2: Prepare new VPS (Destination VPS)<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">2.1. Install Docker &amp; Docker Compose<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">bash<\/p>\n\n\n\n<pre class=\"wp-block-code\" data-no-translation=\"\"><code data-no-translation=\"\">apt update &amp;&amp; apt upgrade -y\napt install docker.io docker-compose-plugin -y\nsystemctl enable docker --now<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">2.2. Create project directory<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">bash<\/p>\n\n\n\n<pre class=\"wp-block-code\" data-no-translation=\"\"><code data-no-translation=\"\">mkdir -p \/opt\/n8n\ncd \/opt\/n8n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Copy the following files into this directory:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code data-no-translation=\"\">docker-compose.yml<\/code><\/li>\n\n\n\n<li><code data-no-translation=\"\">.env<\/code><\/li>\n\n\n\n<li>Backup file (<code data-no-translation=\"\">n8n.sql<\/code>, <code data-no-translation=\"\">n8n-data.tar.gz<\/code>)<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Step 3: Restore data on new VPS<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">3.1. Restore n8n data<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>With bind mount:<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">bash<\/p>\n\n\n\n<pre class=\"wp-block-code\" data-no-translation=\"\"><code data-no-translation=\"\">tar -xzvf n8n-data.tar.gz -C \/<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>With Docker volume:<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">bash<\/p>\n\n\n\n<pre class=\"wp-block-code\" data-no-translation=\"\"><code data-no-translation=\"\">docker volume create n8n_data\n\ndocker run --rm \\\n  -v n8n_data:\/data \\\n  -v $(pwd):\/backup \\\n  alpine tar xzvf \/backup\/n8n-data.tar.gz -C \/<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">3.2. Restore database<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>PostgreSQL:<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">bash<\/p>\n\n\n\n<pre class=\"wp-block-code\" data-no-translation=\"\"><code data-no-translation=\"\">docker compose up -d postgres\ndocker exec -i postgres \\\n  psql -U n8n n8n &lt; n8n.sql<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>MySQL:<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">bash<\/p>\n\n\n\n<pre class=\"wp-block-code\" data-no-translation=\"\"><code data-no-translation=\"\">docker compose up -d mysql\ndocker exec -i mysql \\\n  mysql -u n8n -p n8n &lt; n8n.sql<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Step 4: Start n8n<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Start n8n:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">bash<\/p>\n\n\n\n<pre class=\"wp-block-code\" data-no-translation=\"\"><code data-no-translation=\"\">docker compose up -d<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Monitor logs to check:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">bash<\/p>\n\n\n\n<pre class=\"wp-block-code\" data-no-translation=\"\"><code data-no-translation=\"\">docker compose logs -f n8n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">When you see the following log line, n8n has started successfully:<\/p>\n\n\n\n<pre class=\"wp-block-code\" data-no-translation=\"\"><code data-no-translation=\"\">Editor is now accessible via:<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Step 5: Fix domain &amp; webhook (THE MOST EASILY FORGOTTEN STEP)<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">5.1. Update .env file<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">env<\/p>\n\n\n\n<pre class=\"wp-block-code\" data-no-translation=\"\"><code data-no-translation=\"\">N8N_HOST=n8n.domainmoi.com\nN8N_PROTOCOL=https\nWEBHOOK_URL=https:\/\/n8n.domainmoi.com\/<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then restart n8n:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">bash<\/p>\n\n\n\n<pre class=\"wp-block-code\" data-no-translation=\"\"><code data-no-translation=\"\">docker compose restart n8n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">5.2. Test webhook<\/h3>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Open workflow with webhook<\/li>\n\n\n\n<li>Click <strong>Test URL<\/strong><\/li>\n\n\n\n<li>Test by calling with browser or curl<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">If webhook errors, it is almost certainly due to <code data-no-translation=\"\">WEBHOOK_URL<\/code> not being configured correctly.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Step 6: Post-migration checklist<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>\u2705 Successfully logged into n8n<\/li>\n\n\n\n<li>\u2705 Workflow is still sufficient<\/li>\n\n\n\n<li>\u2705 Credentials not showing red<\/li>\n\n\n\n<li>\u2705 Webhook works normally<\/li>\n\n\n\n<li>\u2705 Cron workflow runs on schedule<\/li>\n\n\n\n<li>\u2705 Execution history is intact<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Common errors when migrating n8n<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Error<\/th><th>Cause<\/th><th>How to handle<\/th><\/tr><\/thead><tbody><tr><td>Credentials error<\/td><td>Lost encryption key<\/td><td>Reuse old key<\/td><\/tr><tr><td>Webhook returns 404<\/td><td>Wrong <code data-no-translation=\"\">WEBHOOK_URL<\/code><\/td><td>Set correct domain<\/td><\/tr><tr><td>No workflow found<\/td><td>DB not restored yet<\/td><td>Re-import SQL<\/td><\/tr><tr><td>n8n restarts continuously<\/td><td>Wrong DB env<\/td><td>Check again <code data-no-translation=\"\">.env<\/code><\/td><\/tr><tr><td>OAuth error<\/td><td>Change domain<\/td><td>Re-authorize<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Real-world experience<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Always stop n8n before backup:<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">bash<\/p>\n\n\n\n<pre class=\"wp-block-code\" data-no-translation=\"\"><code data-no-translation=\"\">docker compose stop n8n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Some other notes:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Production environment should use PostgreSQL instead of SQLite<\/li>\n\n\n\n<li>Save file <code data-no-translation=\"\">.env<\/code> to password manager to avoid loss<\/li>\n\n\n\n<li>Take VPS snapshot before migrate to be able to rollback if needed<\/li>\n\n\n\n<li>If using queue mode, remember to migrate Redis as well<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Migrating n8n to a new VPS will be completely safe if you ensure all 4 pillars:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Database<\/strong> \u2013 Contains all workflows and executions<\/li>\n\n\n\n<li><strong>n8n data<\/strong> \u2013 Credentials and configuration<\/li>\n\n\n\n<li><strong>Encryption key<\/strong> \u2013 To decrypt credentials<\/li>\n\n\n\n<li><strong>Webhook<\/strong> \u2013 Ensure domain and URL are correct<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">If you're having trouble during the migration process or need further support, don't hesitate to leave a comment below. Let me know:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Are you using PostgreSQL or MySQL?<\/li>\n\n\n\n<li>Single n8n or queue mode?<\/li>\n\n\n\n<li>Are you using reverse proxy (Nginx \/ Traefik \/ Cloudflare)?<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">I will support and provide specific commands suitable for your stack!<\/p>","protected":false},"excerpt":{"rendered":"<p>Migrating n8n from the old VPS to the new VPS sounds simple, but in reality, if you miss a few important steps, you may face serious errors such as losing credentials, webhooks not working, or even the entire workflow disappearing. This article is written based on [\u2026]<\/p>","protected":false},"author":1,"featured_media":35013,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1,1205],"tags":[],"class_list":["post-35012","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","category-n8n-marketing"],"acf":[],"rankmath_keywords":{"primary":"","secondary":[""]},"yoast_keywords":{"primary":"","secondary":[]},"yoast_focuskw":"","rankmath_focuskw":"","seo_keywords":{"primary":"","secondary":[""]},"_links":{"self":[{"href":"https:\/\/dps.media\/en\/wp-json\/wp\/v2\/posts\/35012","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/dps.media\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dps.media\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dps.media\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/dps.media\/en\/wp-json\/wp\/v2\/comments?post=35012"}],"version-history":[{"count":0,"href":"https:\/\/dps.media\/en\/wp-json\/wp\/v2\/posts\/35012\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/dps.media\/en\/wp-json\/wp\/v2\/media\/35013"}],"wp:attachment":[{"href":"https:\/\/dps.media\/en\/wp-json\/wp\/v2\/media?parent=35012"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dps.media\/en\/wp-json\/wp\/v2\/categories?post=35012"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dps.media\/en\/wp-json\/wp\/v2\/tags?post=35012"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}