How I use VeraCrypt to keep my data secure

The zsh aliases and functions I use to make working with VeraCrypt faster every day.

I’ve been using VeraCrypt for encrypted vaults for a while now. I mount and dismount vaults multiple times a day, and typing out the full command each time gets old fast: --text, --mount, --pim=0, --keyfiles="", --protect-hidden=no. There’s nothing wrong with the CLI, it’s just repetitive, and repetitive is what aliases are for.

The GUI exists, but I spend most of my time in a terminal and launching a GUI app to mount a file feels like leaving the house to check if the back door is locked. So I wrote some aliases and functions. They’ve replaced the GUI for me entirely.

Before getting into the aliases: VeraCrypt is the right tool for this specific job, but it’s worth being clear about what that job is. I’m encrypting discrete chunks of data stored as container files, not entire drives. If I wanted to encrypt a USB pen drive or an external hard disk, I’d use LUKS instead, which is better suited to full-device encryption on Linux. VeraCrypt’s strength is the container format: a single encrypted file that you can copy anywhere, sync to cloud storage, and open on almost any platform. I format my vaults as exFAT specifically for this: it works on Windows, macOS, Linux, and iOS via Disk Decipher. That cross-platform use case is what makes it worth the extra ceremony.

This post covers what I ended up with and why. It’s worth saying upfront: this works for me, for my use case, right now. It doesn’t follow that it’s the right fit for anyone else. LUKS, Cryptomator, and plenty of other tools solve similar problems in different ways, and any of them might be a better fit depending on what you’re trying to do. I’m not attached to this setup permanently either. If something better comes along, or my requirements change, I’ll adapt.

The basics: mount and list

The two simplest aliases are vcl to list what’s currently mounted, and vcc to create new vaults:

alias vcl='veracrypt -l'
alias vcc='veracrypt --text --create --filesystem=exFAT --encryption=AES --hash=SHA-512 --pim=0 --keyfiles=""'

vcm is a full function because it needs to handle a few things: creating the mount directory, defaulting to the current directory if no path is specified, and (when only one vault is mounted in total) automatically cd-ing into it so I can get straight to work:

vcm() {
    [[ -z "$1" ]] && { echo "Usage: vcm <volume.tc|vc> [mountpoint]"; return 1; }
    local vol="$1"
    local mnt="${2:-${PWD}/$(basename "${vol%.*}")}"
    proton-drive-sync pause 2>/dev/null && echo "Proton sync paused"
    dropbox-cli stop &>/dev/null && echo "Dropbox stopped"
    mkdir -p "$mnt"
    if veracrypt --text --mount --pim=0 --keyfiles="" --protect-hidden=no "$vol" "$mnt"; then
        local count
        count=$(veracrypt -l 2>/dev/null | wc -l)
        [[ "$count" -eq 1 ]] && cd "$mnt" && ls -la
    else
        rmdir "$mnt" 2>/dev/null
        echo "Failed to mount $vol"
        proton-drive-sync resume 2>/dev/null
        dropbox-cli start &>/dev/null &
        return 1
    fi
}

The auto-cd only triggers when it’s the sole mounted vault. If I’ve already got other vaults open, it stays out of the way. Both sync clients are paused before mounting to prevent them trying to upload a vault that’s actively being written to — a reliable way to end up with a corrupted or conflicted file.

Mounting everything at once

I keep several vault files in the same directory, so vcma was a natural next step: mount all .tc and .vc files in a given directory with a single shared password:

vcma() {
    local dir="${1:-$PWD}"
    local vaults=("$dir"/*.tc(N) "$dir"/*.vc(N))

    [[ ${#vaults[@]} -eq 0 ]] && { echo "No .tc/.vc files found in $dir"; return 1; }

    echo -n "Password: "
    read -rs pass
    echo

    local mounted=0
    for vol in "${vaults[@]}"; do
        local mnt="${dir}/$(basename "${vol%.*}")"
        mkdir -p "$mnt"
        if veracrypt --text --mount --pim=0 --keyfiles="" --protect-hidden=no -p "$pass" "$vol" "$mnt" 2>/dev/null; then
            echo "Mounted: $(basename "$vol")$mnt"
            (( mounted++ ))
        else
            echo "Failed:  $(basename "$vol")"
            rmdir "$mnt" 2>/dev/null
        fi
    done

    pass=""
    echo "$mounted/${#vaults[@]} mounted"
}

The (N) glob qualifier in zsh means the glob returns nothing (rather than erroring) if no files match. Worth knowing if you’re adapting this for bash, where you’d handle the empty case differently.

Dismounting

Dismounting is where I hit the most friction. The vcu function handles both single-volume and all-at-once dismounting, and cleans up the mount directories afterwards:

vcu() {
    if [[ -z "$1" ]]; then
        local mounts
        mounts=($(veracrypt -l 2>/dev/null | awk '{print $4}'))
        if [[ ${#mounts[@]} -eq 0 ]]; then
            echo "No volumes mounted"
            return 1
        fi
        # If we're inside a mounted vault, cd out before dismounting
        for mnt in "${mounts[@]}"; do
            if [[ "$PWD/" == "$mnt/"* ]]; then
                echo "Leaving vault: $mnt"
                cd ~
                break
            fi
        done
        veracrypt -d
        for mnt in "${mounts[@]}"; do
            [[ -d "$mnt" ]] && rmdir "$mnt" 2>/dev/null
        done
    else
        local vol
        vol="$(realpath "$1")"
        local mnt
        mnt=$(veracrypt -l 2>/dev/null | grep -F "$vol" | awk '{print $4}')
        if [[ -z "$mnt" ]]; then
            echo "$1 is not mounted"
            return 1
        fi
        veracrypt -d "$vol"
        rmdir "$mnt" 2>/dev/null
    fi
}

The vcua alias just calls vcu with no arguments: dismount everything, clean up the directories.

The bit I added most recently is the cd ~ before dismounting. If I’m working inside a vault and run vcua, the dismount would fail silently because the directory was in use. The fix checks whether $PWD is under any of the mounted paths and steps out first. The trailing slash on both sides ("$PWD/" == "$mnt/"*) avoids the edge case where one vault path is a prefix of another.

Tab completion

One more thing that makes this feel native rather than bolted on: tab completion for mounted volumes when running vcu, and completion for .tc/.vc files when using vcm or vcma:

_vc_vault_files() { _files -g "*.tc"; _files -g "*.vc" }
_vc_mounted() {
    local -a vols
    vols=($(veracrypt -l 2>/dev/null | awk '{print $2}'))
    compadd "$@" -- "${vols[@]}"
}
compdef _vc_vault_files vcm vcma
compdef _vc_mounted vcu

Hidden volumes and plausible deniability

One feature worth mentioning, even if I don’t use it daily: VeraCrypt supports hidden volumes. The idea is that you create a second encrypted volume inside the free space of an existing one. The outer volume gets a decoy password and some plausible-looking files. The hidden volume gets a separate password and your actual sensitive data.

When VeraCrypt mounts, it tries the password you entered against the standard volume header first, then checks whether it matches the hidden volume header. Because VeraCrypt fills all free space with random data during creation, an observer cannot tell whether a hidden volume exists at all. It’s indistinguishable from random noise.

In practice: if you’re ever compelled to hand over your password, you hand over the outer volume’s password. Nothing in the file itself proves there’s anything else there. This is what “plausible deniability” means in this context. It’s not a feature most people will ever need, but it exists and it’s well-implemented.

Where the vault files live: Dropbox, not Proton Drive

My vault files are stored in Dropbox rather than Proton Drive, which I realise sounds odd given that Proton Drive is the more privacy-focused option. The reason is practical: the Proton Drive iOS app fails to sync VeraCrypt vaults reliably.

The developer of Disk Decipher (an iOS VeraCrypt client) recently dug into this and was incredibly helpful in tracking down the cause. Looking at the Proton Drive app logs, he found: "Draft revision already exists for this link". The hypothesis is that VeraCrypt creates revisions faster than Proton Drive’s file provider can handle. What makes it worse is that the problem surfaces immediately: just mounting a vault and dismounting it again is enough to trigger the error. That’s a single write operation. There’s no practical workaround on the iOS side.

It’s an annoying trade-off. Dropbox has significantly more access to my files at the infrastructure level, but the vault files themselves are encrypted before they ever leave the machine, so what Dropbox sees is opaque either way. For now, it works. I’m keeping an eye on Proton Drive’s iOS progress.

Google Drive is an obvious option I haven’t mentioned: that’s intentional. I’m actively working on reducing my Google dependency, so it’s not something I’m considering here. Technically, on Linux, you could use rsync to swap Dropbox out for almost any provider. What keeps me on Dropbox for this specific use case is how it handles large files: it chunks them and syncs only the changed parts rather than re-uploading the whole thing. For vault files that can be several gigabytes, that matters.

As you’ll have noticed in the code above, vcm and vcma both pause Dropbox and Proton Drive before mounting, and vcu restarts them once the last vault is closed. The sync clients fail silently if they’re not running, so the same code works on machines where neither is installed.

A note on the Windows situation

If you’re using VeraCrypt on Windows, there’s a problem coming. The signing certificate used for VeraCrypt builds up to now is from a 2011 CA that expires in June 2026. The current author hasn’t been able to sign new builds, which means new releases can’t be published with a valid signature. Once that certificate expires, Windows will refuse to load unsigned drivers, and the VeraCrypt kernel driver needs to load for any of this to work.

Full system encryption is most at risk: the bootloader sits outside the OS and has to be trusted by the firmware. Booting with driver signing disabled is cumbersome and not something you’d want as a permanent state. But even mounting containers (without full disk encryption) requires the driver, so portable mode and regular vault files are likely affected too. The exact outcome depends on how Windows enforces this over time, but the trajectory isn’t great.

I’m on Linux, so this doesn’t affect me directly. If you are on Windows and relying on VeraCrypt, it’s worth watching the project closely over the next few months and having a migration plan ready. The Reddit thread has more detail if you want the full picture.

The full set at a glance

CommandWhat it does
vclList mounted volumes
vcm vault.vcMount a vault (creates mount dir, cds in if it’s the only one open)
vcmaMount all vaults in current directory with one shared password
vcma ~/pathMount all vaults in a specific directory
vcu vault.vcDismount a specific volume
vcuaDismount everything (cds to ~/ first if you’re inside a vault)
vccCreate a new vault (interactive)

All of these live in my dotfiles.