🚀 Executive Summary
TL;DR: Scripts often fail with ‘command not found’ when run with `sudo` because `sudo` intentionally sanitizes environment variables, including the `$PATH`, for security. The core problem is `sudo` operating with a minimal environment, unaware of custom command locations. Solutions involve either explicitly providing full command paths, preserving the user’s environment with `sudo -E`, or, for a robust fix, modifying the `secure_path` in the `/etc/sudoers` file using `visudo` to include necessary directories.
🎯 Key Takeaways
- When executing commands with `sudo`, the environment, including the `$PATH` variable, is sanitized for security, leading to ‘command not found’ errors for executables not in `sudo`’s `secure_path`.
- Hardcoding absolute paths for commands (e.g., `/usr/bin/psql` instead of `psql`) is a quick but brittle fix, as it breaks if the executable’s location changes.
- The most robust and maintainable solution is to modify the `secure_path` within the `/etc/sudoers` file using `visudo`, appending directories like `/usr/local/bin` to ensure `sudo` can find necessary tools system-wide.
A senior engineer’s guide to fixing the classic “it works for me but fails with sudo” problem by addressing how `sudo` sanitizes environment variables and the `PATH`.
Why Your Script Fails With Sudo: A Field Guide for Junior Ops
I still remember the pager going off at 3:17 AM. It was a Tuesday. A critical database migration script for our main `prod-db-01` cluster was failing, halting the entire release pipeline. The junior engineer on call, bless his heart, was panicking. “Darian, it works perfectly when I run it from my terminal, I swear! But the deployment runner keeps saying ‘command not found’.” We’ve all been there. That sinking feeling when a script that was ‘bulletproof’ five minutes ago suddenly can’t find a basic command like `aws` or `psql`. This isn’t just annoying; it’s a rite of passage. And it almost always comes down to one sneaky culprit: the environment.
The “Why”: Sudo is Wiping Your Environment Clean (And That’s a Good Thing)
When you run a command with sudo, you’re not just running it as the root user. For security reasons, sudo intentionally starts with a minimal, sanitized environment. It throws away most of your current user’s environment variables, including your carefully crafted $PATH. Your interactive shell might know that kubectl lives in /usr/local/bin/, but the stark, clean environment that sudo creates has no idea. It only looks in a very specific, restricted set of directories defined by a setting called secure_path in the /etc/sudoers file.
This is a security feature, not a bug. It prevents a compromised user account’s funky $PATH from tricking the root user into running a malicious script. But knowing that doesn’t make it any less frustrating when your deployment is on fire.
Your Playbook: Three Ways to Fix This Mess
Alright, let’s get you unblocked. Here are three ways to solve this, from the quick-and-dirty to the proper enterprise-grade solution.
Solution 1: The Quick & Dirty Fix (Hardcode the Path)
This is the duct tape of the sysadmin world. It’s not pretty, but it will get the job done when management is breathing down your neck. The idea is simple: don’t rely on the $PATH at all. Find the full, absolute path to your command and use that directly in your script.
First, find the command’s full path using which:
which psql
It might spit out something like /usr/bin/psql. Now, update your script:
Before (Fails with sudo):
psql -h prod-db-01 -U admin -c "VACUUM ANALYZE users;"
After (Works with sudo):
/usr/bin/psql -h prod-db-01 -U admin -c "VACUUM ANALYZE users;"
Warning: This is a brittle solution. If the tool is ever moved or installed in a different location on another server, your script breaks again. Use this to get out of an outage, but promise me you’ll create a ticket to fix it properly later.
Solution 2: The “Do It Right” Fix (Use `sudo -E` or Modify `sudoers`)
This is the approach I’d expect from a mid-level engineer. We address the root cause by telling sudo how to behave.
Option A: Preserve the Environment
The -E flag tells sudo to preserve your existing environment variables, including $PATH. This is incredibly useful but should be used with caution.
sudo -E /path/to/your/script.sh
Option B: Modify the `secure_path` (The Architect’s Choice)
For a truly robust and permanent solution, you can add the directory containing your tools to `sudo`’s trusted `secure_path`. This is done by editing the sudoers file, which you should ALWAYS do with the visudo command to prevent syntax errors that could lock you out of the system.
Run sudo visudo and find the line that looks like this:
Defaults secure_path = /sbin:/bin:/usr/sbin:/usr/bin
Let’s say your tools (like the AWS CLI) are in /usr/local/bin. You just append that to the path:
Defaults secure_path = /sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin
Save and exit. Now, any script run via `sudo` will be able to find executables in `/usr/local/bin` without any changes to the script itself. This is the cleanest, most maintainable solution for your infrastructure.
Solution 3: The ‘Nuclear’ Option (Modifying System-Wide Profiles)
I’m including this for completeness, but I rarely recommend it. You can force an environment variable to be set for all users, including the context `sudo` runs in, by editing files like /etc/environment.
You could open /etc/environment and modify the PATH directly:
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games"
Pro Tip: Don’t do this unless you have a very, very good reason. Modifying system-wide profiles can have unintended consequences, affecting services and users in ways that are difficult to debug. It’s a blunt instrument. Stick with Solution 2 unless you’re in a truly unique situation.
So there you have it. The next time you see “command not found” in a script that ‘should’ work, take a deep breath. It’s probably not your logic; it’s the environment. Now you have a playbook to diagnose it and fix it like a pro.
🤖 Frequently Asked Questions
❓ Why does my script work normally but fail with ‘command not found’ when run with sudo?
`sudo` intentionally starts with a minimal, sanitized environment for security, discarding most of your current user’s environment variables, including `$PATH`. This means commands located outside `sudo`’s `secure_path` are not found.
❓ What are the trade-offs between the different `sudo` environment solutions?
Hardcoding paths is a quick, brittle fix. `sudo -E` preserves the environment but should be used cautiously due to security implications. Modifying `secure_path` via `visudo` is the most robust and maintainable solution for infrastructure, while editing `/etc/environment` is a ‘nuclear’ option with potential unintended system-wide consequences.
❓ What is a common implementation pitfall when modifying `sudoers` for `secure_path`?
A common pitfall is editing the `/etc/sudoers` file directly instead of using the `visudo` command. `visudo` provides syntax checking, which prevents errors that could lock you out of `sudo` access on your system.
Leave a Reply