Host many crypto wallets yourself: safe & effective, for less

Host Bitcoin, Ethereum, Monero and many more safely with LXD
Host Bitcoin, Ethereum, Monero and many more safely with LXD


In 2014 Mt. Gox was the biggest online trading platform for Bitcoin. But hackers got themselves into Mt. Gox servers and transfered almost all Bitcoins out of Mt. Gox. That was the day thousands of people lost all or most of their Bitcoins.
And what if your current trusted online trading platform goes offline too and never comes back, like it happened with some other Bitcoin wallet platform that vanished some years ago. I forgot its name.

That does not have to happen with you. Build your own Fort Knox for all your Coins, at home!

This is what you need:

  • Some old computer with at least 4GB of RAM
  • A bigger HDD with a minimum of 1TB – for the big blockchains
  • Another drive with at least 120GB, an SSD would be best – for the system and the software

This is how my wallet maschine looks like:

HP Z620, 32GB RAM – 550€
WD Black 2TB Retail – 99€ (immer Retail nehmen!)
2x Samsung 860 EVO 250GB (in raid 1) – 2 x 65.90€
2x Orico 2,5″ HDD auf 3.5″ Adapter für SSDs – 2 x 8.99€
Ubuntu 18.04 LTS – 0€


But it doesn’t have to be expensive! And old computer is really good enough. But for PoS currencies you want enough power and memory to let many of them run 24/7.

This is what we are going to do now:

  • Install Ubuntu
  • Install & prepare the software
  • Partition drives and prepare them
  • Create containers for the wallets (with LXD)
  • Mount safe directories to the containers
  • Install the wallets and secure the wallet files


Why use Containers?

Running all wallets directly on the host maschine is perfectly possible and could be secure enough. But seperating different workloads is a common security measure today as there might always be a security flaw that can be misused to escape a safe environment. This does not mean that containers are 100% secure, for sure.

The advantage of containers compared to virtual maschines are ressources are not permanently assigned. So if an application needs ressources it gets it. Also there is no giant overhead by emulating a whole operating system, which is absolutely not needed for this purpose. And installing and preparing the operating system costs too much time as we create many containers. Containers only take the absolute minimum of ressources needed to run.

Established virtual maschine hypervisors in contrast are mostly closed source and cannot be accessed directly but remotely, like VMware ESXi. So you have to access your maschines and wallets from another maschine – which is a big security hazzard for our purpose. You should never access that maschine remotely!


Why use LXD containers?

In comparison docker containers have to be ‘compiled’ and you can’t easily check if they are working as intended. So the advantage here is that you are running full operating systems that you can alter and manipulate at any time.

I’m currently writing a spererate blog post about pros and cons on LXD containers. If it’s done I will put a link here. If it is never going to be done, this text will never disappear – hehe.


Some tips

My recommendation is to connect the maschine via ethernet to the internet as a wireless connection might not be fast enough to sync all blockchains. Further wireless traffic can be spoofed with wireshark and you don’t want that in a safe environment.

Password rules: You should use at least 3 different passwords (sorted from insignificant to important): wallet password (optional), os user password (Linux), cryptsetup password (disk encryption for wallets).

Wallet passwords don’t have to be used as the chance that somebody could access your wallets is very low.

It is not recommended to have an ssh connection open to your machine. Also don’t use wireless keyboards as their data streams are not encrypted and passwords can easily be read!

Review every command you enter! Don’t just copy & paste! You do all this by your own and you are solely responsible for what happens if you do something wrong! So be cautious! Don’t hold me liable for things you do!


What is going to happen

The goal is to seperate mass of data (blockchains and databases) and wallets. As the wallet storage has to be stored encrypted and blockchains don’t need any encryption. Containers cannot access each others files.

We trust nobody. So we create one container for every wallet / currency. Because if one wallet software is flawed then the host and all other wallets will not get harmed.

We use a mountable, encrypted file to store wallets inside it. This is because we want to store this file as a backup without the need to have the backup device to be encrypted, because the file is already an encrypted file system.


Let’s go, finally:

Install Ubuntu or your operating system of choice with one full-size partition as your system disk. We will put the wallets inside a mountable file, so there is no need to 

Partition your big drive for the blockchains and databses with one big partition 

sudo fdisk /dev/sdb (replace ‘sdb’ with your drive !)

n (create new partition)

press enter until the partition is created (to accept default values)

w (write partitions and exit fdisk)

Format that new partition with the filesystem of your choice

sudo mkfs.ext4 /dev/sdb1 (replace ‘sdb1’ with your new partition!)

Let the filesystem be automatically mounted by your os on startup

sudo nano /etc/fstab

And add the following to the file

/dev/sdb1 /cargo ext4 defaults 0 2

The zero is some deprecated flag for backup software and the ‘2’ will invoke a quick filesystem check on startup. ‘/cargo’ is the mount point for the filesystem. Cargo, because it just carries a mass of data.

Now create a mountable file for all the wallets that can fit on your backup drive (USB drive is enough).

sudo fallocate -l 1G /armored.img

This will create a 1G big mountable file called ‘armored’. Wallets are really not that big. Armored, because all wallets have to be guarded. 

Mount the file to a loop device

sudo losetup -f /armored.img

This will find a free loop device to mount the file one. Keep a note of the resulting device! Usually ‘/dev/loop6’.

Now encrypt the file and use a unique password and note the password somewhere secure – write it down on paper!

sudo cryptsetup -c aes-xts-plain64 -s 512 -h sha512 -y luksFormat /dev/loop6 (replace ‘loop6’ with your noted loop device!)

Open the encrypted file by mapping it

sudo cryptsetup luksOpen /dev/loop6 armored (replace ‘loop6’ again!)

This will create a mapper device in /dev/mapper/ with the name ‘armored’. So the final device is ‘/dev/mapper/armored’.

Now create a filesystem of your choice on the mapper device

sudo mkfs.ext4 /dev/mapper/armored


Install & configure the system

sudo apt update (get initial apt sources)

sudo apt install xpra openbox xinit xterm

We need xpra to access a wallet’s window with an encrypted tunnel (via ssh). Openbox is needed to have a minimal desktop to the maschine to access a wallet’s gui. Xinit is needed for the desktop to work. Xterm is a terminal emulator to have a console in desktop mode.

Start the desktop


Open a terminal with right click, “Terminal Window”

Initialize LXD

sudo lxc init

To have all containers to be seperated when accessing mounted devices (‘armored’ and ‘cargo’) we need more uids and gids.

Increase subuids

sudo nano /etc/subuid

And replace root’s and lxd’s right side of the collon ‘:’ from 65536 to 100000000. To have every container own 65536 uids.

Repeat that for the gids

sudo nano /etc/subgid

And replace root’s and lxd’s right side of the collon ‘:’ from 65536 to 100000000. To have every container own 65536 gids.

Both files should now look similar like this:





Restart LXC to apply the changes to subuid and subgid

sudo systemctl restart lxd (apply new u-/gids)


Create the containers

Repeat from this point on to create a new container to host a wallet.

sudo lxc launch ubuntu:bionic crypto-xmr

This will create a container with Ubuntu 18.04 as its base. You whatever os you like.

Now tell LXD that your container should be isolated for the mount points (armored and cargo)

sudo lxc config set crypto-xmr security.idmap.isolated true

Restart the container

sudo lxc restart crypto-xmr

It should say something like “remapping” to indicate that it remaps to the new uids and gids. If it doesn’t say something like this, please check if you did the subuid and subgid step correctly!

Create the cargo directory for your first container and make it writeable to everyone, temporary, to find the container’s user’s uid and gid for access rules.

sudo mkdir /cargo/xmr

sudo chmod 777 /cargo/xmr (do not specify ‘-r’ !)

Do the same for the armored directory

sudo mkdir /armored/xmr

sudo chmod 777 /armored/xmr (do not specify ‘-r’ !)

Create the container, give it a prefix to indicate that this is a wallet and the name of the currency. Like this: ‘crypto’ as prefix and ‘xmr’ for the currency.

Now mount the container’s own cargo and armored directories to the contianer

sudo lxc config device add crypto-xmr cargo disk source=/cargo/xmr path=/cargo

Do the same for armored

sudo lxc config device add crypto-xmr armored disk source=/armored/xmr path=/armored

Enter into the new container

sudo lxc exec crypto-xmr bash

[Note: Every command starting with ‘#’ is inside the container.]

Configure ssh to be accessable by your maschine, so that you can have access to a wallet’s gui.

# nano /etc/ssh/sshd_config (Nedded to access gui with xpra via ssh)

Do these changes for easier access. Your maschine is safe enough to justify these changes. Do not use PubkeyAuthentication for easier configuration. Permit root login and allow password authentication.

replace “#PubkeyAuthentication yes”
with “PubkeyAuthentication no” (remove ‘#’ also)

replace “#PermitRootLogin whatever”
with “PermitRootLogin yes”

replace “#PasswordAuthentication no”
with “PasswordAuthentication yes” (remove ‘#’ also)

(save & exit with Ctrl+X)

Restart or reload ssh to apply these changes 

# systemctl restart sshd

Get initial apt sources for the container

# apt update

If you use Monero wallet – it needs libpcsclite1 to start

# apt install xpra libpcsclite1 (monero wallet needs this library)

Add a new user to run the wallet inside (we do not run a wallet in root!)

# adduser xmr

Add a password to the user. You can share this password with all users you create on any container – because we don’t want password hell.

sudo passwd xmr

Change to your newly created user

# su – xmr

Get the latest wallet (example for monero:)

# wget

# tar -xf linux64

Create a script that will start your wallet with its own ‘desktop’

# nano

And add this

xpra start :20
screen -d -m bash -c “DISPLAY=:20 monero-gui-v0.12.2.0/monero-wallet-gui”

(save with Ctrl + X)

This will start a new ‘desktop’, ‘:20’ and the wallet software in the background, that can be remotely accessed via ssh.

Make the script executable

# chmod +x

And start the wallet to test if it works

# ./ (Note: errors are not critical for monero…)

Something does not start? remove “screen -d -m” from the ‘’ script to see more information. 

Note the container’s ip address (Note that you have to issue the following commands on your main machine, not inside the container)

sudo lxc list

Now start another terminal window on your main machine. Attach to the maschine’s xpra via ssh

xpra attach ssh:xmr@ (replace ‘xmr’ with the container’s created user and the ip with the container’s ip)

Tadaaaa! The wallet gui should now open on your main maschine. Now configure it (leave paths untouched!) and close it – now we replace the blockchain and wallet paths with softlinks to the directories we created earlier. 

Do NOT send any values to this wallet as we will remove the wallet later! Removing the wallet is a safety measure!

You should have the other terminal window, which is currently inside your contaienr, still open. If not, open a new terminal window and issue:

sudo lxc exec crypto-xmr bash

# su – xmr

Now, inside your container, create a test file so that we can find out which uid and gid your ‘xmr’ user has.

# touch /cargo/test (find uid/gid)

Swap to the other terminal window (main machine) to now see the test file’s uid and gid

l -l /cargo/xmr

Note uid / gid (e.g. “65900:65900”) Left side of the collon ‘:’ is the uid, right side the gid.

Now limit the access to your directories for the one container’s user only:

sudo chown <uid>:<gid> /cargo/xmr -R

sudo chown <uid>:<gid> /armored/xmr -R

sudo chmod 770 /cargo/xmr (do not specify ‘-r’ !)

sudo chmod 770 /armored/xmr (do not specify ‘-r’ !)

Now move blockchain and wallet to cargo and armored.

Move the wallet’s data direcotry, which almost always contains both blockchain and wallet, directly into /cargo/, as this container’s ‘/cargo’ is mounted to the main machine’s ‘/cargo/xmr’.

# mv ~/.bitmonero/* /cargo/

Remove the empty directory

# rm ~/.bitmonero/ -r

Note: Setting a different data directory in a wallet software’s config file is discuraged. As configuration failures can tend to fall back to default settings, using the default directories, resulting in two different wallets! Very dangerous, so we use defaults!

Because of that, create a soft symlink from cargo to the old directory, so that the wallet software does not see a difference. 

# ln /cargo /home/xmr/.bitmonero -s

REMOVE the original wallet (be sure you don’t have already sent values into it!!) We do this because the wallet was already written to an unencrypted device and could then be extracted from the drive even though the file was deleted.

# rm ~/Monero


Bitcoin (including Bitcoin clones like Litecoin) users have to do these steps:

Sadly, the wallet file must lie within the data directory and must not be a symbolic link. This is bad because we want to seperate blockchain and wallet – because blockchain storage is unencrypted and the wallet should be located on the encrypted mountable file.

Hard linking is not possible, Bitcoin wallet software blocks that. So we start hacking by using bind mounts.

But first create an empty file, named like the wallet: ‘wallet.dat’ inside cargo and armored

# touch /armored/wallet.dat /cargo/wallet.dat

Now bind mount the wallet file. Exit to get back to the container’s root user!

# exit

Now bind mount the wallet file from ‘/armored/wallet.dat’ to ‘/cargo/wallet.dat’. This will pretend that the wallet file lies within the unencrypted cargo directory, but in reality it is not – hehe.

# mount –bind /armored/wallet.dat /cargo/wallet.dat

Info: Don’t worry, root:root owns these soft links created by ‘ln’ and the mod is 777, but nobody can enter the target directory, because the rights and ownership of the target directory matter.

Don’t forget to switch back to your container’s wallet user:

# su – xmr


Back to non-bitcoin wallets:

If you haven’t moved your wallets like in the chapter above, then do this:

Create a soft symlink from /armored to the wallet software’s default data directory – don’t worry, this will not write your wallet into the unencrypted drive.

# ln /armored ~/Monero -s

If your wallet software is using a wallet file, then create a soft symlink from that file to the original place instead. 

# ln /armored/wallet.dat ~/.bitcoin/wallet.dat

Create a soft symlink to the wallet – just like for the data directory

# ln /armored /home/xmr/Monero -s

Done! Your blockchain is now located on your big drive and the wallet inside your safe mountable file.

Now start the ‘’ script again and verify that everything gets saved on the right place. If you stopped the “xpra attach” command already, then issue it again as xpra is needed to see the monero wallet gui on the host.

Check if everything is running by looking at the screen session:

# screen -r

To detach from the screen session (do not exit from it!) with Ctrl+A and Ctrl+D



Your wallet is now super secure. Don’t forget to update your host machine and the containers with apt.

If everything was done correctly, the content of ‘/armored’ on the host machine can be saved by copying the ‘/armored.img’ file to a DVD or USB drive. Better use archive-grade DVDs instead of fragile USB drives.

Questions? Critique? Etc.? Please leave a comment!


How to shutdown a container?

Always exit all terminals and exit the openbox session and log out after you are done. Except for the screen sessions, which have to be running always, just detatch from them.

Before shutting down the host, every wallet has to be gracefully stopped. As most wallets need to safe important pieces the blockchain first (no need to shutdown a container as containers shutdown by themselves if the host shuts down):

To shut a container down the non-gui way:

sudo lxc exec crypto-xmr bash

# su – xmr

# screen -r

# (Ctrl+C & wait until screen exists by itself, meaning that the wallet has closed)

# exit


To shut a container down the gui-way:

Attach to the container via xpra & ssh:

xpra attach ssh:xmr@ (replace ‘xmr’ with the container’s created user and the ip with the container’s ip)


Now you can shutdown the container

sudo lxc stop crypto-xmr


Missing libraries?

Your wallet software is not starting because of a missing library like ‘’?

sudo apt install libasound2


How to update host machine and containers?

sudo apt update

sudo apt full-upgrade

Update the containers:

sudo lxc exec crypto-xmr bash

# apt update

# apt full-upgrade


How to backup the armored.img file?

There are some steps to take BEFORE making a backup for the armored.img file. 

Shutdown all containers. See Chapter “How to shutdown a container?”.

Unmount the armored filesystem

sudo umount /armored

Stop the armored mapper

sudo cryptsetup luksClose armored

Release the loop device from the mountable file

sudo losetup -d /dev/loop6 (replace ‘loop6’ with the coherant loop device! look it up by issuing ‘lsblk’)

For safety, sync everything to the disks

sudo sync

Now your armored.img file is safely unmounted. You can now copy it to your archive device.

IMPORTANT: After copying the file issue the sync command to be 100% sure that everything was written to the archive device!


Questions? Critique? Etc.? Please leave a comment!