How to Securely Manage Passwords and API Keys in Bash Scripts

Slapping -p'password' into your BASH script is a security disaster waiting to happen—anyone running ps -ef can see your credentials in plain text. But how do you automate backups and API calls without compromising your server? From .my.cnf and GPG encryption to Cloud Secret Managers, here are the best practices for handling secrets in Linux.
A montage of symbols and text evocative of passwords, encryption, and encoding.

I recently sat down to write a BASH script to automate my database backups. Everything was going great until I hit a wall: passwords.

We’ve all been there. The easiest thing to do is just slap -p'my_password' right into the command. But if you’ve spent any time in Linux, you know that’s a disaster waiting to happen—anyone running ps -ef on the server can see your password in plain text.

I wanted to do it the right way so I did some digging into best practices, first for MySQL/MariaDB and then for more generic applications. Here is what I found.

MariaDB/MySQL

If you’re specifically dealing with MySQL or MariaDB, you have a few options depending on how paranoid (or professional) you want to be.

Option A: The .my.cnf File

MariaDB can look for a configuration file in the user’s home directory. It’s clean and keeps your script looking tidy.

How to do it: Create a file at ~/.my.cnf with this content:

[client]
user = backup_user
password = "your_secure_password"

Important: Restrict the permissions so other users can’t read your password: chmod 600 ~/.my.cnf

Now, your script is incredibly simple because mysqldump just finds the credentials automatically:

#!/bin/bash
mysqldump --all-databases > /backups/db_backup_$(date +%F).sql

Option B: mysql_config_editor

If you hate the idea of your password sitting in a plain text file (even with restricted permissions), MariaDB has a tool called mysql_config_editor. This creates an obfuscated login file (.mylogin.cnf) that humans can’t read.

Run this once manually:

mysql_config_editor set --login-path=backup_service --host=localhost --user=backup_user --password

Then, your script just references the “path”:

#!/bin/bash
mysqldump --login-path=backup_service --all-databases > /backups/db_backup_$(date +%F).sql

Option C: Environment Variables

In Docker or Kubernetes, config files are a pain. The standard here is to use environment variables injected by a Secret Manager (like HashiCorp Vault or AWS Secrets Manager).

#!/bin/bash
export MYSQL_PWD="${DB_PASSWORD}"
mysqldump -u backup_user --all-databases > /backups/db_backup.sql
unset MYSQL_PWD

Tips for DB Backups:

  • Don’t use root: Create a dedicated backup_user with only the permissions they need (SELECT, LOCK TABLES, etc.).
  • Compress on the fly: Don’t waste disk space. Pipe your dump straight into gzip: mysqldump --login-path=backup_service --all-databases | gzip > /backups/db_$(date +%F).sql.gz

Everything Else

While I was researching the database stuff, I wondered: “What do I do for my other API keys and passwords?” Since those tools don’t always have a built-in .my.cnf equivalent, I found three common patterns.

A Secrets File

Similar to the .my.cnf method, you create a generic config file and source it into your script.

Create ~/.secrets.conf:

API_KEY="sk_live_51Mz..."
FTP_PASSWORD="my-ftp-password"

Secure it with chmod 600 ~/.secrets.conf

Then, in your script:

#!/bin/bash
if [ -f ~/.secrets.conf ]; then
    source ~/.secrets.conf
else
    echo "Error: Secret file not found."
    exit 1
fi

curl -H "Authorization: Bearer $API_KEY" https://api.example.com/data

The pass Utility

If you want real encryption, check out pass. It uses GPG to encrypt your passwords on disk. They only exist in plain text in your memory for the split second they are called.

Once set up, your script looks like this:

#!/bin/bash
API_KEY=$(pass backup/api_key)
curl -H "Authorization: Bearer $API_KEY" https://api.example.com/data

Secret Managers

If you’re working in AWS, Azure, or GCP, stop storing passwords on the hard drive entirely. Use a Secret Manager. Your script makes an API call to the vault at runtime to grab the secret.

Example using the AWS CLI:

#!/bin/bash
DB_PASS=$(aws secretsmanager get-secret-value --secret-id prod/db/password --query SecretString --output text)
my_custom_tool --password "$DB_PASS"

Which one should you choose?

It really comes down to your environment:

  • If you’re running a small project or a single VPS, the secrets.conf + chmod 600 method seems perfectly fine. It’s simple and miles better than hard-coding.
  • Personally, I started using pass as a result of this investigation; it’s not that hard to set up and you end up with at-rest encryption.
  • If you’re running something like Docker Swarm or Kubernetes, use Environment Variables injected by your orchestrator.
  • In a corporate setting, I’d use a dedicated Secret Manager (Vault, AWS Secrets Mgr) for the auditing and rotation features.

One last warning: Be careful how you use the variable once you have it. Avoid passing passwords as command-line arguments if possible (like my_tool --pass $API_KEY), because those are still visible in the process list (ps -ef). Whenever possible, use environment variables or password files.

Stay secure! 🤓

Leave a Reply

Your email address will not be published. Required fields are marked *

Secret Link