Cart

    Sorry, we could not find any results for your search querry.

    Handling sensitive credentials securely in Linux

    When a script needs a password, API key, token, or private secret, the worst place to put it is directly in the source code. Hardcoded credentials are easy to leak through Git commits, backups, screenshots, logs, shell history, and copied files.

    A secure setup on Linux usually follows one rule:

    store the secret outside the script, and inject it only at runtime.

    This article explains 10 of the safest common options to using credentials securely in Linux, when to use them, and how to implement them in Python.


     

    Why hardcoding secrets is dangerous

     

    Let's look at the danger of hardcoding secrets based on an example. This is what not to do:

    DB_PASSWORD = "SuperSecretPassword123"

    Even in a private repo, that password can end up in:

    • Git history
    • pull requests
    • code review tools
    • editor autosaves
    • backups
    • chat screenshots
    • logs if accidentally printed
    • AI conversations if you expose your codebase to an LLM

    Even if you later remove it, Git may still contain the old value in previous commits.


     

    General security principles of handling credentials

     

    Before looking at specific methods, these principles apply everywhere:

    1. Keep secrets out of source code: Your script should only refer to a variable name like DB_PASSWORD, not the actual password.
       
    2. Restrict access: Only the user or service account that needs the secret should be able to read it.
       
    3. Never pass secrets on the command line: Avoid commands like: python3 app.py --password mysecret. Command-line arguments can be visible in process listings and logs.
       
    4. Never log secrets: Do not print them for debugging, and be careful not to include them in exceptions.
       
    5. Prefer tokens over passwords when possible: If a service supports API tokens, short-lived credentials, or IAM-based access, those are often better than static passwords.
       
    6. Rotate secrets: Even well-protected secrets can leak eventually. Rotation limits the damage.
       
    7. Use gitignore: If for example you use a .env file, add it to gitignore
       
    8. Load passwords from an environment variable or protected file (at minimum)
       
    9. File permissions: set file permissions to 600

     

    Which option should you use?

     

    In this article we deal with 10 options for handling sensitive credentials securely in Linux. For using these options, the general guidelines help you pick the best option:

     

    For local development

    • Python keyring
    • .env file kept out of Git
    • Environment variable set interactively

     

    For a manual admin script

    • getpass
    • Python keyring

     

    For a server running a Linux service

    • Systemd credentials
    • Systemd with a protected environment file

     

    For containers

    • Docker/Kubernetes secrets
    • Or your platform’s secret manager

     

    For serious production

    • A dedicated secret manager
    • Or.. at least systemd credentials / mounted secret files

     

    Option 1: Using environment variables

     

    Environment variables are one of the most common approaches on Linux and keep the secret out of your code.

     

    Pros, cons & use cases of using environment variables

    Pro Con Best use case
    Simple Environment variables may be visible to privileged users or via debugging tools local development
    widely supported They can leak into logs, crash dumps, or child processes if not handled carefully CI/CD
    easy for automation They are often fine, but not the strongest option for high-security environments containers
    keeps secret out of the source code  

    temporary script execution

     


     

    Python example

    import os
    
    def main():
        db_user = os.getenv("DB_USER", "appuser")
        db_host = os.getenv("DB_HOST", "localhost")
        db_password = os.getenv("DB_PASSWORD")
    
        if not db_password:
            raise RuntimeError("DB_PASSWORD is not set")
    
        print(f"Connecting to {db_host} as {db_user}")
        # connect_to_db(user=db_user, password=db_password, host=db_host)
    
    if __name__ == "__main__":
        main()

    The password is not stored in the script. It is expected from the environment at runtime.


     

    Secure ways to set the variable

     

    Good: prompt interactively

    read -s -p "DB_PASSWORD: " DB_PASSWORD; echo
    export DB_PASSWORD
    python3 app.py
    unset DB_PASSWORD

    This avoids putting the password into shell history.


     

    Riskier: inline assignment

    DB_PASSWORD='mypassword' python3 app.py

    This is convenient, but not ideal on shared systems because the secret appears in the shell command you typed and may be stored in shell history.


     

    Option 2: Protected file with strict permissions

     

    Another good Linux-native option is to store secrets in a separate file that is readable only by the correct user.This is often better than scattering secrets across shell profiles.

     

    Pros, cons & use cases of using protected files with strict permissions

    Pros Cons Use cases
    Very Linux-friendly The secret is still plaintext at rest personal servers
    simple to manage on servers File permissions must be correct simple automation
    works well with backup and permission controls Easy to accidentally copy or back up insecurely system accounts
        controlled VM environments

     

    Example secret file

     

    For example, we could have a secret file at this location:

    ~/.config/myapp/secrets.env

    The file could have the following contents:

    DB_USER=appuser
    DB_HOST=localhost
    DB_PASSWORD=replace_me

    To protect the file with strict permissions, you'd for example set permissions such as these:

    mkdir -p ~/.config/myapp
    chmod 700 ~/.config/myapp
    chmod 600 ~/.config/myapp/secrets.env

    This means:

    • The directory is only accessible by your user
    • The file is only readable/writable by your user

     

    Python example that uses the secret file

     

    You can either load the secret file from the shell before starting Python, or read it directly.

     

    Method A: source it in the shell

    set -a
    source ~/.config/myapp/secrets.env
    set +a
    python3 app.py

     

    Method B: read from the file in Python

    from pathlib import Path
    
    def load_secrets_file(path):
        data = {}
        for line in Path(path).read_text().splitlines():
            line = line.strip()
            if not line or line.startswith("#"):
                continue
            key, value = line.split("=", 1)
            data[key.strip()] = value.strip()
        return data
    
    def main():
        secrets = load_secrets_file(Path.home() / ".config/myapp/secrets.env")
        db_password = secrets.get("DB_PASSWORD")
        db_user = secrets.get("DB_USER", "appuser")
        db_host = secrets.get("DB_HOST", "localhost")
    
        if not db_password:
            raise RuntimeError("DB_PASSWORD missing in secrets file")
    
        print(f"Connecting to {db_host} as {db_user}")
        # connect_to_db(user=db_user, password=db_password, host=db_host)
    
    if __name__ == "__main__":
        main()

     

    Option 3: .env file for development only

     

    A env file is a common developer convenience pattern. It is fine for development if handled carefully, but it should not be treated as a high-security production secret store.

     

    Pros, cons & use cases .env for development only

    Pros Cons Use cases
    Very convenient for development Easy to commit by mistake local development
    common ecosystem pattern Plaintext file demos
      Not the best production choice non-production setups

     

    Example .env file

    DB_USER=appuser
    DB_HOST=localhost
    DB_PASSWORD=replace_me

     

    Python example with python-dotenv

     

    Step 1

    Install python-dotenv:

    pip install python-dotenv

     

    Step 2

    Create a script implementing python-dotenv, for example:

    import os
    from dotenv import load_dotenv
    
    load_dotenv()
    
    def main():
        db_user = os.getenv("DB_USER", "appuser")
        db_host = os.getenv("DB_HOST", "localhost")
        db_password = os.getenv("DB_PASSWORD")
    
        if not db_password:
            raise RuntimeError("DB_PASSWORD is not set")
    
        print(f"Connecting to {db_host} as {db_user}")
        # connect_to_db(user=db_user, password=db_password, host=db_host)
    
    if __name__ == "__main__":
        main()

     

     

    Commit a template instead of an actual .env file containing secrets, for example a file called env.example with the following contents:

    DB_USER=appuser
    DB_HOST=localhost
    DB_PASSWORD=

    Then each developer creates their own local env file based on the example file.


     

    Option 4: Prompt at runtime with getpass

     

    If a human runs the script manually, the safest simple option is often not to store the password at all.

     

    Pros, cons & use cases of using prompt at runtime with getpass

    Pros Cons Use cases
    Very simple Not usable for automation one-off scripts
    Nothing stored at rest Still lives in memory while the process runs admin tools
    Good for occasional admin scripts   local manual tasks

     

    Python example

    from getpass import getpass
    
    def main():
        db_user = "appuser"
        db_host = "localhost"
        db_password = getpass("Enter DB password: ")
    
        print(f"Connecting to {db_host} as {db_user}")
        # connect_to_db(user=db_user, password=db_password, host=db_host)
    
    if __name__ == "__main__":
        main()

    The password:

    • is not shown while typing
    • is not stored in the script
    • does not need to be saved on disk

     

    Option 5: Linux desktop secret store via Python keyring

     

    For developer laptops and workstations, a better solution is to use the OS credential store. On Linux, Python’s keyring library can use:

    • Secret Service API
    • GNOME Keyring
    • KWallet
    • other supported backends

     

    Pros, cons & use cases of using a desktop secret store via Python keyring

    Pros Cons Use cases
    Better than plaintext files Can be harder to set up on headless servers Local development on Linux desktop
    Integrates with desktop security mechanisms Backend support varies by environment Personal workstation scripts
    Easy for local user workflows   Developer tooling

     

    Using a Python keyring

     

    Step 1

    Install Python keyring:

    pip install keyring

     

    Step 2

    Store a password once:

    import keyring
    from getpass import getpass
    
    service_name = "myapp-db"
    username = "appuser"
    
    password = getpass("Enter DB password to store: ")
    keyring.set_password(service_name, username, password)
    
    print("Password stored securely.")

     

    Step 3

    Retrieve the password which is stored in the keyring in your app

    import keyring
    
    def main():
        service_name = "myapp-db"
        username = "appuser"
    
        db_password = keyring.get_password(service_name, username)
        if not db_password:
            raise RuntimeError("No password found in keyring")
    
        print("Password loaded from keyring")
        # connect_to_db(user=username, password=db_password, host="localhost")
    
    if __name__ == "__main__":
        main() 

     

    Option 6: systemd service environment files

     

    For production Linux services, systemd is often the right place to inject secrets. Instead of storing the password in the script, you put it in a protected file and let systemd provide it to the process.

     

    Pros, cons & use cases of using a systemd service

    Pros Cons Use cases
    Fits standard Linux service deployment Secret still exists in plaintext on disk VM-based production apps
    Keeps secret outside code Requires careful permissions and ops hygiene background daemons
    Good access control via filesystem permissions   internal services on Linux

     

    Example application with systemd service environment files

     

    Use the same environment-based Python script from earlier.

     

    Step 1

    Create a secret file, for example /etc/myapp/myapp.env and give it the following contents:

    DB_USER=appuser
    DB_HOST=localhost
    DB_PASSWORD=replace_me 

     

    Step 2

    Adjust the permissions of the directory and files used in step 1, for example: 

    sudo mkdir -p /etc/myapp
    sudo chown root:root /etc/myapp
    sudo chmod 755 /etc/myapp
    
    sudo chown root:myapp /etc/myapp/myapp.env
    sudo chmod 640 /etc/myapp/myapp.env  

    The file is owned by root, but readable by the myapp group.


     

    Step 3

    Next, create a service unit file, for example etc/systemd/system/myapp.service

    [Unit]
    Description=My Python App
    After=network.target
    
    [Service]
    User=myapp
    Group=myapp
    EnvironmentFile=/etc/myapp/myapp.env
    ExecStart=/usr/bin/python3 /opt/myapp/app.py
    Restart=on-failure
    
    [Install]
    WantedBy=multi-user.target  

     

    Step 4

    Start the service you just created:

    sudo systemctl daemon-reload
    sudo systemctl enable --now myapp.service  

     

    Option 7: systemd credentials (more secure than plain env files)

     

    If you want something stronger than a simple environment file under systemd(option 6), the credential mechanism is worth considering. It avoids some of the drawbacks of standard environment variables.

    Instead of exposing secrets as normal environment variables, the secret is provided to the service through credential files at runtime. Why is this better?

    • It avoids normal environment variable exposure
    • Cleaner separation between app and secret source
    • More appropriate for serious service deployments

    Using systemd credentials is aimed at more security-conscious Linux service deployments and production services managed by systemd. That asides, most of the pros, cons and use cases of using a systemd service also apply here. 


     

    Service unit example

    [Unit]
    Description=My Secure Python App
    After=network.target
    
    [Service]
    User=myapp
    Group=myapp
    LoadCredential=db_password:/etc/secretstore/db_password
    ExecStart=/usr/bin/python3 /opt/myapp/app.py
    Restart=on-failure
    
    [Install]
    WantedBy=multi-user.target  

     

    Python example

    from pathlib import Path
    import os
    
    def get_systemd_credential(name: str) -> str:
        creds_dir = os.environ.get("CREDENTIALS_DIRECTORY")
        if not creds_dir:
            raise RuntimeError("CREDENTIALS_DIRECTORY not set")
        return (Path(creds_dir) / name).read_text().strip()
    
    def main():
        db_password = get_systemd_credential("db_password")
        print("Loaded password via systemd credentials")
        # connect_to_db(user="appuser", password=db_password, host="localhost")
    
    if __name__ == "__main__":
        main()  

     

    Option 8: Docker secrets

     

    If your app runs in containers, environment variables are common, but Docker secrets are safer for sensitive values.

     

    Pros, cons & use cases of using Docker secrets

    Pros Cons Use cases
    Better than baking secrets into images Depends on deployment setup Docker Swarm
    Better than plain container env vars in many cases Local development may still use env or env vars Containerized Linux services
        Production container deployments

     

    Example Python code

    from pathlib import Path
    import os
    
    def get_secret():
        secret_file = os.getenv("DB_PASSWORD_FILE", "/run/secrets/db_password")
        return Path(secret_file).read_text().strip()
    
    def main():
        db_password = get_secret()
        print("Loaded password from Docker secret file")
        # connect_to_db(user="appuser", password=db_password, host="db")
    
    if __name__ == "__main__":
        main()  

    Instead of setting DB_PASSWORD directly, you mount a secret file into the container.


     

    Option 9: Kubernetes secrets

     

    Kubernetes Secrets are a standard way to provide secrets to workloads. They are often mounted as files or exposed as env vars. For security, mounted files are usually preferable to environment variables.

     

    Python example

    from pathlib import Path
    
    def main():
        db_password = Path("/var/run/secrets/db_password").read_text().strip()
        print("Loaded password from mounted Kubernetes secret")
        # connect_to_db(user="appuser", password=db_password, host="db")
    
    if __name__ == "__main__":
        main()  

    Important note: Kubernetes Secrets are not automatically "high security" just because they are called secrets. Their protection depends on:

    • etcd encryption
    • RBAC
    • pod security
    • cluster configuration
     

     

    Option 10: Cloud or external secret managers

     

    For larger or production-grade systems, dedicated secret managers are often the best option, for example Google Secret Manager or HashiCorp Vault. These can offer:

    • Access control
    • Auditing
    • Secret rotation
    • Temporary credentials
    • Central management

    Dedicated secret managers are best for:

    • Production infrastructure
    • Multiple services
    • Regulated environments
    • Teams needing auditability and rotation

     

    Generic Python pattern

    def get_secret_from_manager():
        # Placeholder for Vault / AWS / Azure / GCP SDK logic
        return "retrieved_secret"
    
    def main():
        db_password = get_secret_from_manager()
        print("Loaded password from external secret manager")
        # connect_to_db(user="appuser", password=db_password, host="db")
    
    if __name__ == "__main__":
        main()  

     

    A combined Python example

     

    This version supports multiple secure sources in a sensible order:

    • environment variable
    • systemd credential
    • local secret file
    • interactive prompt
    import os
    import sys
    from pathlib import Path
    from getpass import getpass
    
    def get_password() -> str:
        pw = os.getenv("DB_PASSWORD")
        if pw:
            return pw
    
        creds_dir = os.getenv("CREDENTIALS_DIRECTORY")
        if creds_dir:
            cred_file = Path(creds_dir) / "db_password"
            if cred_file.exists():
                return cred_file.read_text().strip()
    
        secret_file = Path.home() / ".config/myapp/db_password"
        if secret_file.exists():
            return secret_file.read_text().strip()
    
        if sys.stdin.isatty():
            return getpass("Enter DB password: ")
    
        raise RuntimeError("No password source available")
    
    def main():
        db_user = os.getenv("DB_USER", "appuser")
        db_host = os.getenv("DB_HOST", "localhost")
        db_password = get_password()
    
        print(f"Connecting to {db_host} as {db_user}")
        # connect_to_db(user=db_user, password=db_password, host=db_host)
    
    if __name__ == "__main__":
        main()  

    This keeps the password out of the script while allowing secure setups in different environments.


     

    Common mistakes when handling secure credentials

     

    • Putting secrets in Git and relying on gitignore: gitignore only helps before a file is committed. Once committed, the secret is in history.
       
    • Storing secrets in shell startup files: Putting production passwords in bashrc or zshrc is often messy and overly broad. Those files load for many shell sessions and can be exposed in backups or support bundles.
       
    • Using world-readable files: A secret file with 644 permissions is not a secret file. Use: chmod 600 secretfile  
       
    • Printing configuration dictionaries: This can accidentally expose values like passwords and API keys.
       
    • Reusing the same password everywhere: If one system leaks, all others are affected.

    Need help?

    Receive personal support from our supporters

    Contact us