{"id":35747,"date":"2026-01-11T17:00:02","date_gmt":"2026-01-11T10:00:02","guid":{"rendered":"https:\/\/dps.media\/huong-dan-di-doi-migrate-supabase-self-hosted-sang-vps-moi-bao-mat-2026\/"},"modified":"2026-01-11T17:34:21","modified_gmt":"2026-01-11T10:34:21","slug":"guide-to-migrate-supabase-self-hosted-to-new-vps-security-2026","status":"publish","type":"post","link":"https:\/\/dps.media\/en\/guide-to-migrate-supabase-self-hosted-to-new-vps-security-2026\/","title":{"rendered":"Guide to Migrating Supabase Self-Hosted to New VPS &amp; Security 2026"},"content":{"rendered":"<p>Operating a system <strong>Supabase Self-Hosted<\/strong> gives you absolute control over data and costs. However, at some point, your current VPS may become overloaded, or you find another VPS provider with better performance at a bargain price. That's when you need to think about <strong>Migrate (Move)<\/strong> your system to a new home.<\/p>\n\n\n\n<p>Many of you worry that this migration will be complicated, prone to data loss or damaging stable configurations. Especially when you already have a significant amount of user data (Users), Database, and Storage. Additionally, there's the concern about prolonged downtime (service interruption) during the transition.<\/p>\n\n\n\n<p>Don't worry! This article will be the most detailed guide, instructing you step by step on how to migrate Supabase Docker from the old VPS to the new VPS in the safest way. In particular, we will use the method <strong>VPS-to-VPS transfer<\/strong> to ensure the fastest speed, and set up <strong>Nginx Proxy Manager<\/strong> to manage domains as well as secure your Dashboard (Studio).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 1: Understand Clearly the \u201cHeart\u201d of Supabase Self-Hosted<\/h2>\n\n\n\n<p>Before getting started, you need to understand the file structure of Supabase to know what to copy and what can be skipped. When you install Supabase with Docker, you can see in the folder <code>\/opt\/supabase<\/code> there are many subfolders like <code>apps<\/code>, <code>packages<\/code>, <code>examples<\/code>{\"translations\":[]}<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img decoding=\"async\" src=\"https:\/\/dps.media\/wp-content\/uploads\/mcp\/2026\/01\/folder_structure_diagram_1768125348402.jpg\" alt=\"Supabase Docker Folder Structure\" title=\"\"><figcaption>Only the \u201cdocker\u201d folder is truly important<\/figcaption><\/figure>\n<\/div>\n\n\n<p>In fact: <strong>99% the most important things are compactly contained in the folder <code>docker<\/code><\/strong>. The remaining folders are mostly the source code of the Supabase project (Next.js apps, libraries\u2026) that Docker Container <strong>does not use directly<\/strong> to run. Docker runs based on pre-built \u201cImages\u201d (for example) <code>supabase\/studio:latest<\/code>, <code>supabase\/postgres:15<\/code>, so copying the source code is unnecessary.<\/p>\n\n\n\n<p>In the folder <code>docker<\/code>, we have 3 \u201cimmutable\u201d components:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong><code>docker-compose.yml<\/code><\/strong>: This is the system design. It specifies which services will run (Database, Auth, Storage, Realtime\u2026), which ports are open, and which volumes are mounted.<\/li>\n<li><strong><code>.env<\/code><\/strong>: This is where all your secrets are stored. Database password (<code>POSTGRES_PASSWORD<\/code>), JWT Secret (to generate tokens), API Keys (Anon Key, Service Role Key). Losing this file means losing control of the system.<\/li>\n<li><strong><code>volumes\/<\/code><\/strong>: The most important is <code>volumes\/db\/data<\/code>. This is where the live PostgreSQL data is stored. If you only copy the config files and forget this folder, you'll have a brand new Supabase but... empty.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Step 2: Prepare the \u201cNew Home\u201d (Target VPS)<\/h2>\n\n\n\n<p>You need to prepare a clean new VPS (Fresh Install). The recommended operating system is <strong>Ubuntu 20.04 LTS<\/strong> or <strong>22.04 LTS<\/strong> to ensure stability and best compatibility with Docker.<\/p>\n\n\n\n<p>First, SSH into the new VPS and update the system:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo apt update &amp;&amp; sudo apt upgrade -y<\/code><\/pre>\n\n\n\n<p>Next, install Docker and Docker Compose plugin. The fastest and most standard way is to use Docker's official automatic installation script:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>curl -fsSL https:\/\/get.docker.com -o get-docker.sh\nsudo sh get-docker.sh<\/code><\/pre>\n\n\n\n<p>After installation, check if Docker is running:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo docker --version\nsudo docker compose version<\/code><\/pre>\n\n\n\n<p>If you see the full version, you're ready to receive the data.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 3: Transfer Data (\u201cShoot\u201d Data via Rsync)<\/h2>\n\n\n\n<p>Instead of the traditional method: <em>Download 10GB of data to your personal computer (slow network) -&gt; Upload 10GB to the new VPS (slow network)<\/em>, we will use <strong>Rsync<\/strong> to transfer data directly between 2 VPS. The speed will be extremely fast by utilizing datacenter bandwidth.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img decoding=\"async\" src=\"https:\/\/dps.media\/wp-content\/uploads\/mcp\/2026\/01\/rsync_terminal_command_1768125330398.jpg\" alt=\"Rsync Terminal Transfer Command\" title=\"\"><figcaption>Rsync transfers data safely, supports resume when network is lost<\/figcaption><\/figure>\n<\/div>\n\n\n<p><strong>Why use Rsync instead of SCP?<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Resume:<\/strong> If you're copying 90% and the network drops, running the Rsync command again will continue from 90%. SCP will have to start over.<\/li>\n<li><strong>Preserves attributes:<\/strong> Rsync preserves permissions and timestamps of files, which is very important for Database files.<\/li>\n<li><strong>Data compression:<\/strong> Flag <code>-z<\/code> helps compress data during transfer, saving bandwidth.<\/li>\n<\/ul>\n\n\n\n<p><strong>Implementation:<\/strong><\/p>\n\n\n\n<p>1. <strong>On New VPS<\/strong>: Create the parent directory in advance to avoid copying the wrong path:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Create directory \/opt\/supabase\nmkdir -p \/opt\/supabase<\/code><\/pre>\n\n\n\n<p>2. <strong>On Old VPS<\/strong>: Run the Rsync command below. This command will prompt for the root password of the new VPS.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Run this command FROM the OLD VPS\n# IP_VPS_NEW: Replace with the IP of the new server (e.g., 116.118.x.x)\n\nrsync -avzP \/opt\/supabase\/docker\/ root@IP_VPS_NEW:\/opt\/supabase\/docker\/<\/code><\/pre>\n\n\n\n<p><em>Note the trailing <code>\/<\/code> in the source path is very important. If you write it wrong, the folder <code>docker<\/code> may be nested (for example <code>docker\/docker<\/code>). The above command is the most standard.<\/em><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 4: Launch Supabase in the New Home<\/h2>\n\n\n\n<p>After the Rsync progress bar completes (reaches 100%), congratulations, the entire soul of the old system has moved to the new home. Now wake it up.<\/p>\n\n\n\n<p>On <strong>New VPS<\/strong>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cd \/opt\/supabase\/docker\n\n# (Optional) Check the .env file again to see if any edits are needed\ncat .env\n\n# Start the containers\ndocker compose up -d<\/code><\/pre>\n\n\n\n<p>The first run will take a few minutes for Docker to pull the images. Be patient. After it's done, check the logs to ensure no errors:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker compose logs -f<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Step 5: Configure Nginx Proxy Manager &amp; SSL<\/h2>\n\n\n\n<p>Supabase by default only opens local ports (like 8000, 3000) or internal ports. To allow users to access via a nice domain (<code>https:\/\/api.your-domain.com<\/code>) with SSL security, you need a Reverse Proxy. <strong>Nginx Proxy Manager (NPM)<\/strong> is an excellent choice because it has an intuitive interface.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img decoding=\"async\" src=\"https:\/\/dps.media\/wp-content\/uploads\/mcp\/2026\/01\/nginx_proxy_manager_ui_1768125366079.jpg\" alt=\"Nginx Proxy Manager Dashboard Interface\" title=\"\"><figcaption>Configure 2 separate Proxy Hosts for API and Studio<\/figcaption><\/figure>\n<\/div>\n\n\n<p>You need to create 2 <strong>Proxy Hosts<\/strong> separately:<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">1. API Gateway (Most Important)<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Domain Names:<\/strong> <code>api.your-domain.com<\/code> (or your API domain)<\/li>\n<li><strong>Forward Hostname \/ IP:<\/strong> <code>172.17.0.1<\/code> (This is Docker's default Gateway IP, helping NPM point back to the container on the same host).<\/li>\n<li><strong>Forward Port:<\/strong> <code>8008<\/code> (This is the port of Kong\/API Gateway in Supabase).<\/li>\n<li><strong>Websockets Support:<\/strong> MUST BE ENABLED (Supabase Realtime uses this).<\/li>\n<li><strong>SSL Tab:<\/strong> Request a new Let\u2019s Encrypt Certificate, Force SSL, HTTP\/2 Support.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">2. Studio Dashboard (Management Interface)<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Domain Names:<\/strong> <code>studio.your-domain.com<\/code><\/li>\n<li><strong>Forward Hostname \/ IP:<\/strong> <code>172.17.0.1<\/code><\/li>\n<li><strong>Forward Port:<\/strong> <code>3003<\/code> (Default port of Studio).<\/li>\n<li><strong>Websockets Support:<\/strong> Enable.<\/li>\n<li><strong>SSL Tab:<\/strong> Configure similarly as above.<\/li>\n<\/ul>\n\n\n\n<p>After configuring NPM, the final step is to go to your domain management page (DNS), point the 2 sub-domains above to <strong>IP of the New VPS<\/strong>. Wait about 5-10 minutes for DNS to update.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 6: High-Level Security (Access Lists)<\/h2>\n\n\n\n<p>A major vulnerability of Supabase Self-Hosted community edition is the <strong>Studio Dashboard has no login page<\/strong>. Anyone who knows the path <code>studio.your-domain.com<\/code> can access and edit your data. This is extremely dangerous.<\/p>\n\n\n\n<p>But with Nginx Proxy Manager, we can easily patch this vulnerability using the <strong>Access Lists<\/strong> (Access Restriction).<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img decoding=\"async\" src=\"https:\/\/dps.media\/wp-content\/uploads\/mcp\/2026\/01\/security_access_list_1768125382017.jpg\" alt=\"Secure Studio with Access List\" title=\"\"><figcaption>Add password protection layer for Studio<\/figcaption><\/figure>\n<\/div>\n\n\n<p><strong>How to implement:<\/strong><\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>In NPM, go to the tab <strong>Access Lists<\/strong>, click <strong>Add Access List<\/strong>.<\/li>\n<li>Name it <code>Admin-Only<\/code>.<\/li>\n<li>In the <strong>Authorization<\/strong>, tab, add a Username and Password that only you know.<\/li>\n<li>(Advanced tip) In the tab <strong>Access<\/strong>, you can enter the static IP of your company\/home and select <strong>Action: Allow<\/strong>. Then enable the option <strong>Satisfy Any<\/strong>.\n<ul><li>Meaning: If it's the correct \u201chome\u201d IP, access directly without any questions.<\/li><li>If it's an unfamiliar IP (like at a cafe), the system will show a popup asking for password.<\/li><\/ul>\n<\/li>\n<li>Save.<\/li>\n<li>Go back <strong>Proxy Hosts<\/strong>, edit the line <code>studio.your-domain.com<\/code>. At the Details tab, in the <strong>Access List<\/strong>, select <code>Admin-Only<\/code>.<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p>Moving the server is not as scary as you think if we understand the nature of Docker and know how to use tools like Rsync. With the guide above, you will not only successfully migrate but also upgrade the security system for your Supabase.<\/p>\n\n\n\n<p>Remember to always backup data periodically (cronjob backup folder <code>docker<\/code>) to prevent unexpected issues. Wish you success!<\/p>\n<style>\r\n.lwrp.link-whisper-related-posts{\r\n            \r\n            margin-top: 40px;\nmargin-bottom: 30px;\r\n        }\r\n        .lwrp .lwrp-title{\r\n            \r\n            \r\n        }.lwrp .lwrp-description{\r\n            \r\n            \r\n\r\n        }\r\n        .lwrp .lwrp-list-container{\r\n        }\r\n        .lwrp .lwrp-list-multi-container{\r\n            display: flex;\r\n        }\r\n        .lwrp .lwrp-list-double{\r\n            width: 48%;\r\n        }\r\n        .lwrp .lwrp-list-triple{\r\n            width: 32%;\r\n        }\r\n        .lwrp .lwrp-list-row-container{\r\n            display: flex;\r\n            justify-content: space-between;\r\n        }\r\n        .lwrp .lwrp-list-row-container .lwrp-list-item{\r\n            width: calc(33% - 20px);\r\n        }\r\n        .lwrp .lwrp-list-item:not(.lwrp-no-posts-message-item){\r\n            \r\n            max-width: 150px;\r\n        }\r\n        .lwrp .lwrp-list-item img{\r\n            max-width: 100%;\r\n            height: auto;\r\n            object-fit: cover;\r\n            aspect-ratio: 1 \/ 1;\r\n        }\r\n        .lwrp .lwrp-list-item.lwrp-empty-list-item{\r\n            background: initial !important;\r\n        }\r\n        .lwrp .lwrp-list-item .lwrp-list-link .lwrp-list-link-title-text,\r\n        .lwrp .lwrp-list-item .lwrp-list-no-posts-message{\r\n            \r\n            \r\n            \r\n            \r\n        }@media screen and (max-width: 480px) {\r\n            .lwrp.link-whisper-related-posts{\r\n                \r\n                \r\n            }\r\n            .lwrp .lwrp-title{\r\n                \r\n                \r\n            }.lwrp .lwrp-description{\r\n                \r\n                \r\n            }\r\n            .lwrp .lwrp-list-multi-container{\r\n                flex-direction: column;\r\n            }\r\n            .lwrp .lwrp-list-multi-container ul.lwrp-list{\r\n                margin-top: 0px;\r\n                margin-bottom: 0px;\r\n                padding-top: 0px;\r\n                padding-bottom: 0px;\r\n            }\r\n            .lwrp .lwrp-list-double,\r\n            .lwrp .lwrp-list-triple{\r\n                width: 100%;\r\n            }\r\n            .lwrp .lwrp-list-row-container{\r\n                justify-content: initial;\r\n                flex-direction: column;\r\n            }\r\n            .lwrp .lwrp-list-row-container .lwrp-list-item{\r\n                width: 100%;\r\n            }\r\n            .lwrp .lwrp-list-item:not(.lwrp-no-posts-message-item){\r\n                \r\n                max-width: initial;\r\n            }\r\n            .lwrp .lwrp-list-item .lwrp-list-link .lwrp-list-link-title-text,\r\n            .lwrp .lwrp-list-item .lwrp-list-no-posts-message{\r\n                \r\n                \r\n                \r\n                \r\n            };\r\n        }<\/style>\r\n<div id=\"link-whisper-related-posts-widget\" class=\"link-whisper-related-posts lwrp\">\r\n            <div class=\"lwrp-title\">Related Posts<\/div>    \r\n        <div class=\"lwrp-list-container\">\r\n                                <div class=\"lwrp-list lwrp-list-row-container lwrp-list-double-row\">\r\n                <div class=\"lwrp-list-item\"><a href=\"https:\/\/dps.media\/en\/christmas-eve-marketing-campaign-24-12-unique-meaningful\/\" class=\"lwrp-list-link\"><span class=\"lwrp-list-link-title-text\">Unique and meaningful Christmas Day Marketing Campaign 24\/12<\/span><\/a><\/div><div class=\"lwrp-list-item\"><a href=\"https:\/\/dps.media\/en\/tiktok-ads-conversion-tracking-standard-guide\/\" class=\"lwrp-list-link\"><span class=\"lwrp-list-link-title-text\">TikTok Ads Conversion Tracking: Official Guide<\/span><\/a><\/div><div class=\"lwrp-list-item\"><a href=\"https:\/\/dps.media\/en\/how-to-leverage-the-trend-of-young-millennials-and-gen-z-conquering-the-sport-of-golf\/\" class=\"lwrp-list-link\"><span class=\"lwrp-list-link-title-text\">How to leverage the millennial and Gen Z trends dominating the golf industry?<\/span><\/a><\/div>                <\/div>\r\n                            <div class=\"lwrp-list lwrp-list-row-container lwrp-list-double-row\">\r\n                <div class=\"lwrp-list-item\"><a href=\"https:\/\/dps.media\/en\/what-is-local-map-services-learn-about-prominent-local-map-solutions\/\" class=\"lwrp-list-link\"><span class=\"lwrp-list-link-title-text\">What are Local Map Services? Learn about outstanding local map solutions<\/span><\/a><\/div><div class=\"lwrp-list-item\"><a href=\"https:\/\/dps.media\/en\/email-marketing-service-solution-to-double-sales-effectively\/\" class=\"lwrp-list-link\"><span class=\"lwrp-list-link-title-text\">Email Marketing Service: Effective Marketing Solutions for Businesses<\/span><\/a><\/div><div class=\"lwrp-list-item\"><a href=\"https:\/\/dps.media\/en\/experience-buying-and-selling-facebook-groups-safely-without-losing-money-unfairly\/\" class=\"lwrp-list-link\"><span class=\"lwrp-list-link-title-text\">Experience buying and selling Facebook groups safely without losing money<\/span><\/a><\/div>                <\/div>\r\n                <\/div>\r\n<\/div>","protected":false},"excerpt":{"rendered":"<p>Running a Supabase Self-Hosted system gives you absolute control over data and costs. However, at some point, your current VPS may become overloaded, or you find another VPS provider with better performance [\u2026]<\/p>","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[],"class_list":["post-35747","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"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\/35747","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=35747"}],"version-history":[{"count":1,"href":"https:\/\/dps.media\/en\/wp-json\/wp\/v2\/posts\/35747\/revisions"}],"predecessor-version":[{"id":35750,"href":"https:\/\/dps.media\/en\/wp-json\/wp\/v2\/posts\/35747\/revisions\/35750"}],"wp:attachment":[{"href":"https:\/\/dps.media\/en\/wp-json\/wp\/v2\/media?parent=35747"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dps.media\/en\/wp-json\/wp\/v2\/categories?post=35747"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dps.media\/en\/wp-json\/wp\/v2\/tags?post=35747"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}