Moving n8n from Zeabur to a VPS: Lose the Encryption Key and Every Credential Is Gone (Tested)

The riskiest part of an n8n migration is N8N_ENCRYPTION_KEY. In our tests, importing with the wrong key still reported success. How to find the key, export and import, plus a ready-to-use Docker Compose and Caddy setup.

Key points

  • n8n encrypts credentials with an encryption key; change the key and none of them can be decrypted.
  • The key is either in the N8N_ENCRYPTION_KEY environment variable or in the config file inside the n8n folder.
  • Tested: importing with the wrong key still says success. Verify with export:credentials --decrypted.
  • Includes a ready-to-use Docker Compose + Caddy setup with automatic HTTPS.
How was this verified? (4 tested, 3 from official docs)
Tested ourselves
  • Official n8n 2.43.7 image: export with export:workflow and export:credentials, then import into a new n8n
  • Same encryption key: credentials decrypt. Different key: import reports success, but decryption fails later
  • Without the environment variable, n8n generates a key and writes it to /home/node/.n8n/config
  • The docker-compose.yml and Caddyfile in this post: started locally as n8n.localhost; HTTPS through Caddy reached n8n and healthz returned 200
Written from official docs, not run ourselves
  • Running commands and downloading files in the Zeabur dashboard: written from Zeabur's official docs; we did not operate a Zeabur account
  • Where Zeabur's n8n template stores data, and whether it uses SQLite or PostgreSQL: check your own service's settings
  • Automatic Let's Encrypt certificates on a real domain: the local test used Caddy's self-signed certificate; production behavior follows Caddy's docs
Contents (6 sections)
  1. The one thing that matters most: find your encryption key
  2. Step 1: Export on Zeabur
  3. Step 2: Set up the new n8n on a VPS
  4. Step 3: Import
  5. Step 4: Verify the credentials really decrypt
  6. Step 5: Wrap up

The one thing that matters most: find your encryption key

Every credential n8n stores (API keys, OAuth tokens, database passwords) is encrypted with one encryption key. Move to a new machine with a different key and none of the credentials can be decrypted; you have to re-enter every one.

The key lives in one of two places:

  1. The N8N_ENCRYPTION_KEY environment variable: check your Zeabur service’s Variables tab
  2. The config file in the n8n folder: if the variable isn’t set, n8n generates a key on first start and writes it here. In the official image the path is /home/node/.n8n/config, and it looks like this:
{
	"encryptionKey": "a random string"
}

Copy the key into your password manager.

A trap we found in testing: importing with the wrong key raises no error.

We exported credentials with KEY_A and imported them into an n8n configured with KEY_B. It printed Successfully imported 1 credential. and everything looked fine. Only when the credential was used did this appear:

Credentials could not be decrypted. The likely reason is that a different "encryptionKey" was used to encrypt the data.

So always do the verification in Step 4 below. Don’t trust the import success message.

Step 1: Export on Zeabur

According to Zeabur’s docs, the Overview tab of a service has a Command section for running commands inside the container. Export the workflows and credentials into the n8n folder:

n8n export:workflow --all --output=/home/node/.n8n/workflows.json
n8n export:credentials --all --output=/home/node/.n8n/credentials.json

The Files section on the same tab lets you browse and download files in the container. Download these two files and config.

The exported credentials.json is encrypted; you can’t read the passwords in it. In our test, the SECRET123 value we had set could not be found anywhere in the file. That is why it only works together with the original key.

Alternative: back up the whole volume. If your n8n uses the built-in SQLite (the folder contains database.sqlite), the offline backup on Zeabur’s Backup tab can download the whole folder. According to Zeabur’s docs you must suspend the service first (Settings → Suspend Service), and that’s right: a running SQLite has a -wal file, and copying without stopping can produce incomplete data.

Step 2: Set up the new n8n on a VPS

Create an Ubuntu VPS, install Docker, point your domain’s A record (for example n8n.example.com) at the VPS IP, and create these two files:

# docker-compose.yml
services:
  n8n:
    image: docker.n8n.io/n8nio/n8n
    restart: unless-stopped
    environment:
      - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}   # reuse the key from Zeabur
      - N8N_HOST=n8n.example.com
      - N8N_PROTOCOL=https
      - WEBHOOK_URL=https://n8n.example.com/
    volumes:
      - n8n_data:/home/node/.n8n
  caddy:
    image: caddy:2
    restart: unless-stopped
    ports: ["80:80", "443:443"]
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
volumes:
  n8n_data:
  caddy_data:
# Caddyfile
n8n.example.com {
  reverse_proxy n8n:5678
}
echo "N8N_ENCRYPTION_KEY=the key copied from Zeabur" > .env
docker compose up -d

Caddy requests the HTTPS certificate automatically, provided DNS already points at this VPS and ports 80 and 443 are open. When n8n runs behind a reverse proxy, n8n’s docs require WEBHOOK_URL so the webhook URLs it generates are the correct public ones.

Step 3: Import

docker compose cp workflows.json n8n:/tmp/
docker compose cp credentials.json n8n:/tmp/
docker compose exec n8n n8n import:workflow --input=/tmp/workflows.json
docker compose exec n8n n8n import:credentials --input=/tmp/credentials.json

Step 4: Verify the credentials really decrypt

docker compose exec n8n n8n export:credentials --all --decrypted --output=/tmp/check.json
docker compose exec n8n cat /tmp/check.json
  • You can read the plain values: the key is correct
  • You see Credentials could not be decrypted: wrong key; go back to the first section

Delete the file immediately afterwards; it contains plain-text passwords:

docker compose exec n8n rm /tmp/check.json /tmp/credentials.json

Step 5: Wrap up

  • Open each workflow and check its trigger; for webhook-triggered ones, update the URL in the other service (LINE, Stripe, form tools)
  • Given Zeabur’s security incidents, we recommend replacing every third-party API key stored in your credentials (see the security post)
  • Set up daily backups: at minimum, archive the n8n_data volume every day and store it somewhere else

For multi-service tools such as Dify or LobeChat, use the docker-compose.yml from their official repositories and add Caddy the same way. For databases, see the database post.

FAQ

Where is the n8n encryption key?

If the N8N_ENCRYPTION_KEY environment variable is set, it is there. If not, n8n generates one on first start and writes it to the config file in the n8n folder (/home/node/.n8n/config in the official image).

n8n says the credentials imported successfully. Why don't they work?

Because the encryption key is different. n8n does not check the key on import; the error Credentials could not be decrypted only appears when the credentials are used. Import again with the original key.