The terminal is your gateway to cloud infrastructure. Whether you are connecting to an EC2 instance via SSH, troubleshooting Lambda container environment issues, or configuring ECS tasks, you need to be proficient in operating Unix-like systems. This guide covers the essential Linux command line skills that cloud engineers will use in their daily work.
By the end of this article, you will have a basic understanding of the terminal, shell, file systems, permissions, and package management. You will know how to use command line tools to access remote servers, troubleshoot issues, and automate workflows.
Prerequisites
-
Unix-like environment (Linux, macOS, or Windows with WSL installed)
-
Familiarity with basic operations of a text editor
-
AWS account (optional, for cloud-specific examples)
Understanding the Terminal and Shell
The terms “terminal,” “shell,” and “command line” are often used interchangeably. Let me explain what each term means and why it is important to distinguish them.
What is a Terminal?
A terminal is a program that receives text input and displays text output. Historically, terminals were physical devices—keyboards and monitors connected to mainframe computers. Today, you can use terminal emulators like Ghostty, iTerm2, or the built-in “Terminal” on macOS.
When you open a terminal application, a prompt will appear where you can enter commands. The terminal itself does not interpret these commands; it is only responsible for handling input and output.
What is a Shell?
A shell is the program that actually processes commands. When you type <span>echo "Hello World"</span> and press Enter, the shell reads this text, evaluates it, executes it, and then returns the output. This cycle is called REPL: Read, Evaluate, Print, Loop.
The two most common shells are:
-
bash (Bourne Again Shell) — the default shell for most Linux distributions
-
zsh (Z Shell) — the default shell for modern macOS
Both are powerful scripting languages capable of handling variables, conditional statements, loops, and functions. All examples in this guide can be run in both bash and zsh.
Why is This Important for AWS?
When you connect to an EC2 instance via SSH, you are actually connecting to a shell session on that remote machine. When configuring Lambda container images, you often write shell scripts. When troubleshooting ECS task failures, you need to check shell output logs.
Understanding the shell means understanding how your cloud infrastructure executes commands.
Let me demonstrate. Open the terminal and run:
$ echo "Hello World"
The shell reads this command, recognizes <span>echo</span> as a program, passes <span>"Hello World"</span> as an argument, and prints the result. Now try:
$ expr 123456 + 7890
Your output will show:
131346
The shell can perform arithmetic operations, string manipulations, and complex logical operations. This is why shell scripts are the foundation of DevOps automation.
File System Navigation
Cloud servers are essentially Linux machines. You need to navigate their file systems to configure applications, view logs, and troubleshoot issues.
Your Current Location
Your shell always has a “working directory” — the folder you are currently in. You can check it with the following command:
$ pwd
This will print your working directory. On macOS, you might see:
/Users/cindy
On Linux systems:
/home/cindy
This is your user directory, where your personal files are stored.
File Paths
A file path is a textual representation of a file’s location in the directory tree. All absolute paths start from the root directory ( <span>/</span> ).
In a Linux system, your user home directory might be <span>/home/achar</span>, which means:
-
Start from the root directory
<span>/</span> -
Enter the
<span>home</span>directory -
Enter the
<span>achar</span>directory
Each directory is separated by a slash.
Listing Files
<span>ls</span> command lists the contents of a directory:
$ ls
You can see the files and directories in your current location. Adding flags can customize the output:
$ ls -l
This will display a “full” format file listing that includes permissions, owners, file sizes, and modification dates. Adding the <span>-a</span> flag will show hidden files:
$ ls -la
Hidden files start with a dot ( <span>.</span> ). Configuration files, such as <span>.bashrc</span> or <span>.zshrc</span>, are hidden by default.
Changing Directories
Use <span>cd</span> to navigate the file system:
$ cd /var/log
This will move you to <span>/var/log</span>, where system logs are stored. Check your new location:
$ pwd
The output shows <span>/var/log</span>.
To go back to the previous directory, use the special alias <span>..</span> :
$ cd ..
Now you are under <span>/var</span>. To return to your home directory from anywhere:
$ cd ~
The tilde ( <span>~</span> ) is an alias for your home directory.
Relative vs Absolute Paths
Absolute paths start with <span>/</span> and can be used from anywhere:
$ cd /var/log/nginx
Relative paths start from your current location:
$ cd nginx
This method only works if the <span>nginx</span> directory exists in your current location.
AWS Context: EC2 Log Files
When you connect via SSH to an EC2 instance running a web application, you often need to check logs. Application logs are typically located at:
-
<span>/var/log</span>– System and service logs -
<span>/var/log/nginx</span>or<span>/var/log/apache2</span>– Web server logs -
<span>/var/log/mysql</span>– Database logs
To view your web server error logs:
$ cd /var/log/nginx
$ ls -l
You will see files like <span>access.log</span> and <span>error.log</span>. Whether troubleshooting locally or on a remote server, this workflow is the same.
Handling Files
You will need to constantly read, create, modify, and delete files. Let me introduce you to some basic commands.
Reading File Contents
<span>cat</span> command prints the entire file:
$ cat /etc/hosts
This will display the hostname mappings of the system. For large files, <span>cat</span> will display everything on the screen, leading to information overload.
Reading Partial Contents
For large files, use <span>head</span> to view the first few lines:
$ head -n 10 /var/log/syslog
This shows the first 10 lines. Similarly, <span>tail</span> shows the last few lines:
$ tail -n 20 /var/log/syslog
<span>-n</span> flag specifies the number of lines.
Following Log Files
When troubleshooting issues with running applications, you need to view newly written log entries. Use <span>tail -f</span> :
$ tail -f /var/log/nginx/access.log
It will continuously display new lines. Press <span>Ctrl+C</span> to stop.
Searching Within Files
<span>grep</span> command is used to search for text patterns:
$ grep "error" /var/log/syslog
This command will print all lines containing “error”. By default, the search is case-sensitive. To perform a case-insensitive search:
$ grep -i "error" /var/log/syslog
To search recursively through a directory:
$ grep -r "database connection" /var/log
This will search all files in <span>/var/log</span> and its subdirectories.
Finding Files
<span>find</span> command can find files by name or pattern:
$ find /var/log -name "*.log"
This will list all files ending with <span>.log</span> in the <span>/var/log</span> directory. The asterisk ( <span>*</span> ) is a wildcard that matches any character.
To find files modified in the last 24 hours:
$ find /var/log -mtime -1
Creating Files
<span>touch</span> command creates an empty file:
$ touch test.txt
If <span>test.txt</span> already exists, <span>touch</span> will update its modification timestamp but will not change its content.
Creating Directories
Use <span>mkdir</span> to create a new directory:
$ mkdir project
To create nested directories in one command:
$ mkdir -p project/src/components
<span>-p</span> flag will create parent directories as needed.
Moving and Renaming
<span>mv</span> command can both move and rename files:
$ mv old-name.txt new-name.txt
To move a file to another directory:
$ mv config.json /etc/myapp/
Copying Files
<span>cp</span> command is used to copy files:
$ cp source.txt destination.txt
To recursively copy directories:
$ cp -r source-dir destination-dir
Deleting Files
<span>rm</span> command is used to delete files:
$ rm unnecessary-file.txt
To delete a directory and its contents:
$ rm -r old-directory
Warning: The command line has no recycle bin. Deleted files will be permanently lost.
AWS Context: S3 and Local Files
When using S3, you often need to sync files between EC2 instances and S3 buckets. The AWS CLI uses some common commands:
$ aws s3 cp local-file.json s3://my-bucket/data/
This operation will copy the local file to S3. To download:
$ aws s3 cp s3://my-bucket/data/config.json ./
These patterns are similar to standard file operations. Understanding local file operations makes cloud storage operations intuitive.
Permissions and Users
Unix-like systems have a powerful permission management system. This is crucial for HIPAA-compliant architectures, as access to protected health information (PHI) must be restricted.
Understanding Permission Strings
After running <span>ls -l</span>, you will see permission strings like this:
-rw-r--r-- 1 achar staff 1234 Nov 13 09:30 file.txt
drwxr-xr-x 5 achar staff 160 Nov 13 09:31 directory
Let me break down the first string: <span>-rw-r--r--</span>
The first character indicates the type:
-
<span>-</span>= regular file -
<span>d</span>= directory
The remaining nine characters are divided into three groups, each with three:
-
Owner Permissions (read/write): the owner of the file
-
Group Permissions (r–): users in the file’s group
-
Other Permissions (r–): everyone else
Each group has three types of permissions:
-
<span>r</span>= read -
<span>w</span>= write -
<span>x</span>= execute
A dash ( <span>-</span> ) indicates that permission is denied.
So <span>-rw-r--r--</span> means:
-
The owner can read and write
-
Group members can read
-
Others can read
-
No one can execute
Checking Your User
Your username:
$ whoami
Check the groups you belong to:
$ groups
Changing Permissions
<span>chmod</span> command is used to modify permissions. Its syntax is as follows:
-
<span>u</span>= user (owner) -
<span>g</span>= group -
<span>o</span>= others -
<span>a</span>= all
Grant execute permission to the owner:
$ chmod u+x script.sh
To remove write permission from others:
$ chmod o-w sensitive-data.txt
Set permissions for everyone:
$ chmod a+r public-file.txt
You can combine changes:
$ chmod u=rwx,g=rx,o=r program.sh
This sets the owner to read/write/execute permissions, the group to read/execute permissions, and others to read-only permissions.
Execute Permissions
For files, execute permission allows the file to be run as a program. For directories, execute permission allows entering that directory.
After downloading a script, you usually need to make it executable:
$ chmod +x deploy.sh
$ ./deploy.sh
<span>./</span> prefix explicitly runs the script in the current directory.
Changing Ownership
<span>chown</span> command is used to change file ownership. This requires higher permissions:
$ sudo chown nginx:nginx /var/www/html/index.html
This changes both the user and group to <span>nginx</span>. The syntax is <span>user:group</span>.
Root User
<span>root</span> user is the superuser with all permissions. Running commands as the root user is powerful but also very dangerous.
<span>sudo</span> command allows you to execute a single command as the root user:
$ sudo systemctl restart nginx
The system will prompt you for a password. After entering the correct password, the command will run with root privileges.
Healthcare Context: HIPAA Compliance
When building systems that comply with HIPAA standards, file permissions are used to protect PHI (protected health information). Database credentials, encryption keys, and patient data files must have restricted permissions.
A secure configuration file might include:
$ chmod 600 /etc/myapp/database.conf
This ensures that only the owner can read or write the file. No other group members or others can access that file.
Your EC2 instance’s IAM role should follow the principle of least privilege—granting only the minimum permissions necessary. Similarly, file permissions should be as strict as possible while ensuring functionality.
Programs and Executable Files
Understanding how programs execute helps troubleshoot deployment issues and write better automation scripts.
Compiled vs Interpreted Programs
Programs fall into two categories:
Compiled Programs are converted into machine code before execution. Languages like Go, Rust, and C generate binary files—executable files containing processor instructions. These files can run directly on your hardware without additional software.
Interpreted Programs require an interpreter to execute. Python scripts need the Python interpreter. Shell scripts need a shell (bash or zsh).
When you run a compiled program:
$ /usr/bin/nginx
The operating system loads that binary file and executes it.
When running an interpreted program:
$ python3 app.py
The Python interpreter reads <span>app.py</span>, parses it, and executes the instructions within.
Shebangs
Shell scripts and Python scripts often start with a shebang—this is a special first line that indicates which interpreter to use:
#!/bin/bash
echo "This is a bash script"
Or for Python:
#!/usr/bin/env python3
print("This is a Python script")
The shebang line tells the operating system which interpreter to call. With the correct shebang line and execute permissions, you can run the script directly:
$ chmod +x script.py
$ ./script.py
If there is no shebang, you need to explicitly specify the interpreter:
$ python3 script.py
Finding Programs
<span>which</span> command shows the location of a program:
$ which python3
The output might show:
/usr/bin/python3
This tells you where the Python interpreter is installed.
AWS Context: Lambda Execution Environment
AWS Lambda functions run in an execution environment (essentially a small Linux container). When deploying Python code to Lambda, AWS provides the Python interpreter. Your code will run as an interpreted program.
For performance-critical workloads, you can deploy compiled binaries as custom Lambda runtimes. For example, Go binaries start faster and use less memory than interpreted Python code.
Understanding this distinction helps you make architectural decisions. Need microsecond response times? Use a compiled language. Need rapid development? Interpreted languages work well.
Environment Variables and PATH
Environment variables are used to configure programs without hardcoding values into the program. The PATH variable is the most important <span>PATH</span> variable you will use.
What is PATH?
<span>PATH</span> is an environment variable that contains a colon-separated list of directories. When you run a command, your shell searches these directories for matching executable files.
Check your path:
$ echo $PATH
You will see something like:
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
This means the shell will search in order:
-
<span>/usr/local/bin</span> -
<span>/usr/bin</span> -
<span>/bin</span> -
<span>/usr/sbin</span> -
<span>/sbin</span>
When you type <span>python3</span>, the shell finds <span>/usr/bin/python3</span> and executes it. You do not need to type the full path.
Adding to PATH
Programs you install often need to be added to the PATH environment variable. To add a directory for the current session:
$ export PATH="$PATH:/home/achar/bin"
This adds <span>/home/achar/bin</span> to your existing PATH environment variable. You can now access programs in that directory by name.
To make this setting permanent, add the export command to your shell configuration file:
-
bash:
<span>~/.bashrc</span> -
zsh:
<span>~/.zshrc</span>
Edit the file:
$ nano ~/.zshrc
Add at the end:
export PATH="$PATH:/home/achar/bin"
Save and exit. New shell sessions will add this directory to the PATH environment variable.
To apply the changes to the current session:
$ source ~/.zshrc
Creating Environment Variables
Use <span>export</span> to set environment variables:
$ export DATABASE_URL="postgresql://localhost:5432/mydb"
Programs can read this variable. In Python:
import os
db_url = os.getenv("DATABASE_URL")
print(f"Connecting to {db_url}")
AWS Context: Lambda Environment Variables
Lambda functions heavily utilize environment variables. You can set environment variables in the AWS console or through infrastructure as code (IaC):
# Lambda function code
import os
import boto3
bucket_name = os.getenv("S3_BUCKET_NAME")
s3 = boto3.client("s3")
In your CloudFormation or Terraform configuration:
resource "aws_lambda_function" "processor" {
function_name = "data-processor"
environment {
variables = {
S3_BUCKET_NAME = "patient-records-bucket"
LOG_LEVEL = "INFO"
}
}
}
This pattern separates configuration from code—crucial for multi-environment deployments (development, testing, production).
Healthcare Context: Secure Credentials
Never hardcode database passwords or API keys into your code. Use environment variables instead:
$ export DB_PASSWORD="$(aws secretsmanager get-secret-value --secret-id prod-db --query SecretString --output text)"
This retrieves the key from AWS Secrets Manager and stores it in an environment variable. Your application can read this variable without exposing the password in the source code.
To comply with HIPAA regulations, audit the use of all environment variables. Ensure sensitive values come from secure sources (e.g., Secrets Manager) rather than plaintext files.
Input, Output, and Streams
Programs communicate through three standard streams: standard input (stdin), standard output (stdout), and standard error (stderr).
Standard Output
When a program prints results, it writes output to <span>echo</span> output (stdout). The echo command demonstrates this:
$ echo "Hello"
The text will appear in your terminal because, by default, standard output is directed there.
Redirecting Output
You can use <span>></span> to redirect standard output to a file:
$ echo "Log entry" > application.log
This will create <span>application.log</span> with the content “Log entry”. If the file already exists, <span>></span> will overwrite it.
To append instead of overwrite, use <span>>></span> :
$ echo "Another entry" >> application.log
Standard Error
Programs write error messages to the standard error stream (stderr), which is a different stream from standard output (stdout). This separation allows you to handle error messages differently from regular output.
Most commands will write errors to standard error output (stderr):
$ ls nonexistent-directory
You will see an error message. To redirect stderr to a file:
$ ls nonexistent-directory 2> errors.log
<span>2></span> syntax redirects stderr (file descriptor 2). To redirect both stdout and stderr simultaneously:
$ command > output.log 2> errors.log
Or to combine them into one file:
$ command > combined.log 2>&1
Standard Input
Programs can read data from standard input. The <span>read</span> command in bash demonstrates this:
#!/bin/bash
echo "What is your name?"
read name
echo "Hello, $name"
When you run this script, it will wait for your input.
Pipes
The pipe operator ( <span>|</span> ) connects the standard output of one program to the standard input of another program:
$ cat application.log | grep "ERROR"
This code reads <span>application.log</span> and filters out lines containing “ERROR”. Pipe operations can create powerful command chains.
Find the 10 most common error messages:
$ grep "ERROR" application.log | sort | uniq -c | sort -rn | head -10
The analysis is as follows:
-
<span>grep</span>extracts error lines -
<span>sort</span>arranges them alphabetically -
<span>uniq -c</span>counts duplicates -
<span>sort -rn</span>sorts them in reverse numerical order -
<span>head -10</span>displays the top 10
AWS Context: CloudWatch Logs
When your Lambda function writes to standard output (stdout), AWS captures it in CloudWatch Logs. Your Python code:
def lambda_handler(event, context):
print(f"Processing event: {event}")
return {"statusCode": 200}
<span>print</span> statement will write content to standard output, which will appear in CloudWatch:
$ aws logs tail /aws/lambda/my-function --follow
Understanding stdout/stderr helps design effective logging strategies. Error messages should be output to stderr, while normal operational information should go to stdout.
Package Managers
Package managers automate software installation, dependency resolution, and updates. They are crucial for maintaining cloud infrastructure.
Linux: apt
On Ubuntu and Debian-based systems, use <span>apt</span> :
$ sudo apt update
This refreshes the package index (the database of available software).
To install a package:
$ sudo apt install nginx
This installs the Nginx web server along with all its dependencies. The package manager:
-
Downloads Nginx and its dependencies
-
Installs all components in the correct locations
-
Configures system paths
-
Sets up systemd services (if applicable)
To remove a package:
$ sudo apt remove nginx
macOS: Homebrew
On macOS systems, Homebrew is the de facto package manager:
$ brew install python3
This installs Python 3 and automatically adds it to your PATH environment variable.
To update all installed packages:
$ brew upgrade
Python: pip
Python has its own package manager:
$ pip install boto3
This installs the AWS SDK for Python. Use <span>requirements.txt</span> to manage project dependencies:
boto3==1.26.137
requests==2.28.2
psycopg2-binary==2.9.5
To install everything in the file:
$ pip install -r requirements.txt
AWS Context: EC2 User Data
When launching an EC2 instance, you can run initialization scripts via user data. This often involves package managers:
#!/bin/bash
apt-get update
apt-get install -y nginx python3-pip
pip3 install boto3
# Configure application
systemctl enable nginx
systemctl start nginx
This script runs when the instance first starts, installing dependencies and starting services.
Healthcare Context: Auditable Deployments
To comply with HIPAA standards, you need auditable deployment processes. Package managers can help by:
-
Explicitly versioning dependencies
-
Creating reproducible environments
-
Tracking what is installed on each server
Your <span>requirements.txt</span> will become part of your compliance documentation, accurately proving which software versions can handle PHI.
Troubleshooting Framework
When things go wrong—and they will—follow this systematic approach.
Step 1: Identify Symptoms
What exactly is broken? Be specific:
-
The application returns a 502 error
-
“Database connection timed out”
-
Lambda function exceeds memory limit
Vague issues like “it doesn’t work” will waste time.
Step 2: Check Logs
Logs contain most of the answers. For web servers:
$ tail -100 /var/log/nginx/error.log
Application logs:
$ journalctl -u myapp.service -n 100
For AWS services, check CloudWatch Logs:
$ aws logs tail /aws/lambda/my-function --since 10m
Step 3: Verify Permissions
Permission issues cause many failures. Check file permissions:
$ ls -l /var/www/html/config.php
Is the owner correct? Are permissions too restrictive?
Check your user permissions:
$ groups
Are you in the right group?
Step 4: Test Connectivity
Network issues are common. Test connectivity:
$ curl https://api.example.com/health
For databases:
$ telnet database.example.com 5432
If the connection fails, check security groups, network access control lists, and firewall rules.
Step 5: Verify Environment Variables
Many issues stem from missing or incorrect environment variables:
$ echo $DATABASE_URL
Step 6: Reproduce Locally
Try to reproduce the issue on your development machine. If it cannot be reproduced locally, the problem lies in environmental factors—most likely server configuration or permission issues.
Real Case: Deploying a Python Web Application
Let me walk you through a complete deployment of these concepts on an EC2 instance.
Configuring the Instance
Launch an Ubuntu 22.04 EC2 instance. Connect via SSH:
$ ssh -i keypair.pem [email protected]
Installing Dependencies
Update the package index:
$ sudo apt update
Install Python and pip:
$ sudo apt install -y python3-pip python3-venv nginx
Creating Application Structure
Create a directory:
$ mkdir -p /home/ubuntu/myapp
$ cd /home/ubuntu/myapp
Create a virtual environment:
$ python3 -m venv venv
$ source venv/bin/activate
Installing Application Dependencies
Create a <span>requirements.txt</span> :
flask==2.3.0
gunicorn==20.1.0
boto3==1.26.137
psycopg2-binary==2.9.5
Install:
$ pip install -r requirements.txt
Configuring Environment Variables
Create a <span>.env</span> file:
export DATABASE_URL="postgresql://user:[email protected]:5432/mydb"
export S3_BUCKET="patient-records-bucket"
export AWS_REGION="us-east-1"
Load it:
$ source .env
Creating the Application
Write to <span>app.py</span> :
from flask import Flask, jsonify
import boto3
import os
app = Flask(__name__)
s3 = boto3.client("s3", region_name=os.getenv("AWS_REGION"))
bucket = os.getenv("S3_BUCKET")
@app.route("/health")
def health():
return jsonify({"status": "healthy"})
@app.route("/files")
def list_files():
response = s3.list_objects_v2(Bucket=bucket, MaxKeys=10)
files = [obj["Key"] for obj in response.get("Contents", [])]
return jsonify({"files": files})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
Test locally:
$ python app.py
Open another terminal to test:
$ curl http://localhost:5000/health
Configuring systemd Service File
Create a systemd service file:
$ sudo nano /etc/systemd/system/myapp.service
Add:
[Unit]
Description=My Flask Application
After=network.target
[Service]
User=ubuntu
WorkingDirectory=/home/ubuntu/myapp
Environment="PATH=/home/ubuntu/myapp/venv/bin"
EnvironmentFile=/home/ubuntu/myapp/.env
ExecStart=/home/ubuntu/myapp/venv/bin/gunicorn --workers 3 --bind 127.0.0.1:5000 app:app
[Install]
WantedBy=multi-user.target
Enable and start:
$ sudo systemctl enable myapp
$ sudo systemctl start myapp
$ sudo systemctl status myapp
Configuring Nginx
Create Nginx configuration:
$ sudo nano /etc/nginx/sites-available/myapp
Add:
server {
listen 80;
server_name _;
location / {
proxy_pass http://127.0.0.1:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Enable the site:
$ sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
$ sudo nginx -t
$ sudo systemctl reload nginx
Verifying Deployment
Test on your local machine:
$ curl http://ec2-xx-xx-xx-xx.compute-1.amazonaws.com/health
You should see:
{"status": "healthy"}
Troubleshooting
If it doesn’t work, check the logs:
$ sudo journalctl -u myapp -n 50
$ sudo tail -50 /var/log/nginx/error.log
Common issues:
-
Permission Denied: Check file permissions with
<span>ls -l</span>. -
Connection Refused: Verify if Gunicorn is running with
<span>systemctl status myapp</span>. -
502 Bad Gateway: Nginx cannot connect to Gunicorn—check the proxy configuration
This workflow demonstrates terminal, shell, file navigation, permissions, environment variables, package management, and troubleshooting—all in a real deployment scenario.