🌐 curl

HTTP From the Command Line — The SysAdmin's Swiss Army Knife for APIs

aider curl fabric fzf gh Copilot jq ollama ripgrep sgpt tmux

What Is curl?

curl is a command-line tool for transferring data with URLs. It supports HTTP, HTTPS, FTP, SFTP, and a dozen other protocols. For SysAdmins, it is the primary tool for testing endpoints, probing APIs, scripting against REST services, and — increasingly — talking directly to AI APIs from the shell.

curl has been on Unix systems since 1997. It is not new. What is new is the role it now plays in AI workflows: hitting local ollama endpoints, calling the Anthropic or OpenAI APIs directly, testing webhook receivers, and scripting AI integrations without a Python environment in the way.

curl is almost certainly already installed on every server you manage. The question is whether you are using it to its potential.

📦 Installation

curl is installed by default on virtually every Linux distribution. Verify first:

curl --version

If for some reason it is missing:

# RHEL / AlmaLinux / Rocky / CentOS
dnf install curl

# Debian / Ubuntu
apt install curl

# Verify after install
curl --version
The version matters more than you might expect. curl 7.76+ added --fail-with-body. curl 7.82+ added --json as a shorthand. AlmaLinux 9 ships with 7.76+. Check your version before assuming newer flags are available.

📖 Basic Usage

Essential Flags Reference

Flag Meaning Common Use
-s Silent — suppress progress meter Script output, piping to jq
-S Show errors even in silent mode Use with -s always: -sS
-o Output to file Download a file
-O Output to file named by URL Quick downloads
-L Follow redirects Almost always use this
-I HEAD request — headers only Check server, content-type, redirects
-v Verbose — show request and response headers Debugging API calls
-X Specify HTTP method -X POST, -X DELETE
-H Add a request header Auth tokens, Content-Type
-d Send POST data Form data, JSON payloads
--json Send JSON body (curl 7.82+) Shorthand for Content-Type + Accept headers
-u Username:password for Basic Auth API basic auth, private repos
-k Skip TLS certificate verification Internal servers with self-signed certs
-w Write-out format string after transfer Print HTTP status code, timing
--max-time Maximum total time in seconds Prevent hung scripts
--retry Retry on transient failure Resilient scripts

The Most Common Patterns

# Simple GET — fetch a page or API endpoint
curl https://example.com/api/status

# Silent GET — pipe output, suppress progress
curl -sS https://example.com/api/status

# Follow redirects (always a good habit)
curl -sSL https://example.com/

# GET with pretty JSON output (pipe to jq)
curl -sS https://api.example.com/v1/info | jq .

# Check headers only (HEAD request)
curl -I https://example.com/

# Verbose — see full request and response headers
curl -v https://example.com/api/

# Download a file
curl -sSL -o /tmp/package.tar.gz https://example.com/downloads/package.tar.gz

# Check HTTP status code only
curl -o /dev/null -sS -w "%{http_code}\n" https://example.com/

POST Requests — Sending Data

# POST form data
curl -sS -X POST -d "user=admin&action=restart" https://example.com/api/control

# POST JSON payload (manual headers)
curl -sS -X POST \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{"key":"value","count":5}' \
  https://api.example.com/v1/endpoint

# POST JSON (curl 7.82+ shorthand)
curl -sS --json '{"key":"value"}' https://api.example.com/v1/endpoint

# POST JSON from a file
curl -sS -X POST \
  -H "Content-Type: application/json" \
  -d @/tmp/payload.json \
  https://api.example.com/v1/endpoint

Authentication

# Basic auth
curl -sS -u username:password https://api.example.com/v1/resource

# Bearer token (API key in header)
curl -sS -H "Authorization: Bearer YOUR_API_KEY" https://api.example.com/v1/resource

# API key as header (varies by service)
curl -sS -H "x-api-key: YOUR_API_KEY" https://api.example.com/v1/resource

🔧 SysAdmin Use Cases

Endpoint Health Checking

# Check if a web service is responding
curl -o /dev/null -sS -w "%{http_code}\n" https://myapp.example.com/health

# Check multiple endpoints in a loop
for host in web01 web02 web03; do
  code=$(curl -o /dev/null -sS -w "%{http_code}" --max-time 5 https://${host}.example.com/health)
  echo "${host}: HTTP ${code}"
done

# Time the response — useful for performance baselining
curl -o /dev/null -sS -w "Connect: %{time_connect}s  Total: %{time_total}s\n" \
  https://myapp.example.com/

# Full timing breakdown
curl -o /dev/null -sS -w \
  "DNS: %{time_namelookup}s  Connect: %{time_connect}s  TLS: %{time_appconnect}s  Total: %{time_total}s\n" \
  https://myapp.example.com/

Checking TLS Certificates

# Verbose output shows certificate details
curl -vI https://example.com 2>&1 | grep -A5 "Server certificate"

# Check expiry — pipe to openssl
echo | openssl s_client -connect example.com:443 2>/dev/null | \
  openssl x509 -noout -dates

# Quick cert check via curl verbose headers
curl -sS --max-time 5 -vI https://example.com 2>&1 | grep "expire date"

Downloading Packages and Releases

# Download and install a binary release
curl -sSL https://github.com/project/releases/latest/download/tool-linux-amd64 \
  -o /usr/local/bin/tool
chmod +x /usr/local/bin/tool

# Download with integrity check (compare sha256)
curl -sSL https://example.com/package.tar.gz -o /tmp/package.tar.gz
curl -sSL https://example.com/package.tar.gz.sha256 -o /tmp/package.tar.gz.sha256
sha256sum -c /tmp/package.tar.gz.sha256

Testing Internal APIs and Services

# Test an internal REST API behind a firewall
curl -sS http://internal-api.corp:8080/v1/status | jq .

# Send a test webhook payload
curl -sS -X POST \
  -H "Content-Type: application/json" \
  -d '{"event":"test","source":"sysadmin-check"}' \
  http://webhook-receiver.corp:9000/webhook

# Test with self-signed cert (internal servers)
curl -sSkL https://internal-secure.corp/api/health

# Test with client certificate
curl -sS --cert /etc/ssl/client.crt --key /etc/ssl/client.key \
  https://mtls-api.example.com/v1/resource

Monitoring and Alerting Scripts

#!/bin/bash
# Simple HTTP monitor with alert
URL="https://myapp.example.com/health"
THRESHOLD=5

code=$(curl -o /dev/null -sS -w "%{http_code}" --max-time ${THRESHOLD} "${URL}" 2>&1)
time=$(curl -o /dev/null -sS -w "%{time_total}" --max-time ${THRESHOLD} "${URL}" 2>&1)

if [ "${code}" != "200" ]; then
  echo "ALERT: ${URL} returned HTTP ${code}" | mail -s "Service Alert" ops@example.com
fi

Interacting with System APIs

# Query the GitHub API for release info
curl -sS https://api.github.com/repos/BurntSushi/ripgrep/releases/latest | \
  jq '.tag_name, .published_at'

# Get your public IP
curl -sS https://ifconfig.me

# Query AWS instance metadata (on EC2)
curl -sS http://169.254.169.254/latest/meta-data/instance-id

# Kubernetes API (with service account token)
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
curl -sS -H "Authorization: Bearer ${TOKEN}" \
  --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
  https://kubernetes.default.svc/api/v1/namespaces/default/pods | jq '.items[].metadata.name'

🤖 curl + AI Tools

This is where curl becomes a first-class citizen in the AI toolchain. Every AI service with an API — Anthropic, OpenAI, local ollama — is reachable with curl. No Python, no SDK, no virtual environment. Just a shell and an API key.

Hitting the Anthropic API Directly

# Basic Claude API call from the shell
curl -sS https://api.anthropic.com/v1/messages \
  -H "x-api-key: ${ANTHROPIC_API_KEY}" \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-opus-4-5",
    "max_tokens": 1024,
    "messages": [
      {"role": "user", "content": "Explain what causes high iowait on Linux in 3 sentences."}
    ]
  }' | jq '.content[0].text'
Store your API key in an environment variable, never hardcoded in scripts. Add export ANTHROPIC_API_KEY="sk-ant-..." to your ~/.bashrc and keep that file permission 600.

Scripting Against the Anthropic API

#!/bin/bash
# ask_claude.sh — pipe any text to Claude from the shell
# Usage: echo "question" | ./ask_claude.sh
#        cat logfile.txt | ./ask_claude.sh

INPUT=$(cat)

curl -sS https://api.anthropic.com/v1/messages \
  -H "x-api-key: ${ANTHROPIC_API_KEY}" \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -d "{
    \"model\": \"claude-opus-4-5\",
    \"max_tokens\": 2048,
    \"messages\": [
      {\"role\": \"user\", \"content\": $(echo "${INPUT}" | jq -Rs .)}
    ]
  }" | jq -r '.content[0].text'
# Use it:
rg "OOM kill" /var/log/messages | ./ask_claude.sh
# or:
echo "What is the difference between RSS and VSZ memory?" | ./ask_claude.sh
The jq -Rs . trick converts multi-line stdin into a properly escaped JSON string. Essential for sending log output or file content as a JSON payload without manual escaping headaches.

Hitting Local ollama Endpoints

# ollama exposes a REST API on localhost:11434
# No API key needed — runs entirely on your server

# Basic ollama query
curl -sS http://localhost:11434/api/generate \
  -d '{
    "model": "llama3",
    "prompt": "What are the most common causes of high load average on Linux?",
    "stream": false
  }' | jq '.response'

# List available models
curl -sS http://localhost:11434/api/tags | jq '.models[].name'

# Check ollama health
curl -sS http://localhost:11434/api/tags > /dev/null && echo "ollama up" || echo "ollama down"

Air-Gapped Log Analysis with curl + ollama

# Full pipeline: rg extracts, curl sends to local LLM, jq formats output
rg -C 3 "segfault\|kernel panic\|OOM" /var/log/messages | \
  jq -Rs '{model: "llama3", prompt: ("Analyze these kernel messages and suggest root cause: " + .), stream: false}' | \
  curl -sS -X POST http://localhost:11434/api/generate \
    -H "Content-Type: application/json" \
    -d @- | \
  jq -r '.response'
The -d @- flag tells curl to read the POST body from stdin. This completes the pipeline: rg → jq (build payload) → curl (send) → jq (extract response). No temp files, no intermediate scripts.

Testing OpenAI-Compatible Endpoints

# OpenAI API — same pattern as Anthropic, different structure
curl -sS https://api.openai.com/v1/chat/completions \
  -H "Authorization: Bearer ${OPENAI_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-4o",
    "messages": [{"role": "user", "content": "Summarize iowait troubleshooting steps."}]
  }' | jq '.choices[0].message.content'

# Any OpenAI-compatible API (LM Studio, LocalAI, vllm) uses the same pattern
# Just change the base URL:
curl -sS http://localhost:1234/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model":"local-model","messages":[{"role":"user","content":"Hello"}]}' | \
  jq '.choices[0].message.content'

curl + jq — The Natural Pairing

# Fetch API response and extract nested field
curl -sS https://api.example.com/v1/servers | jq '.servers[] | {name: .hostname, ip: .ip_address}'

# Filter by condition
curl -sS https://api.example.com/v1/services | jq '.[] | select(.status != "running")'

# Format into a table for humans
curl -sS https://api.github.com/repos/cli/cli/releases | \
  jq -r '.[] | [.tag_name, .published_at[:10], .name] | @tsv' | \
  column -t

curl + rg — Fetch and Filter

# Fetch a remote config and search it
curl -sS https://raw.githubusercontent.com/project/repo/main/config.yaml | \
  rg "timeout|retry|max_conn"

# Fetch a log from a remote API and extract errors
curl -sS https://logservice.corp/api/logs?host=web01 | \
  jq -r '.entries[].message' | \
  rg "ERROR|CRITICAL"

# Monitor a status page for a specific string
curl -sS https://status.example.com/ | rg "Operational\|Degraded\|Outage"

Wrapping AI API Calls in Shell Functions

# Add to ~/.bashrc for quick AI access from any prompt

ask() {
  curl -sS https://api.anthropic.com/v1/messages \
    -H "x-api-key: ${ANTHROPIC_API_KEY}" \
    -H "anthropic-version: 2023-06-01" \
    -H "Content-Type: application/json" \
    -d "{
      \"model\": \"claude-opus-4-5\",
      \"max_tokens\": 1024,
      \"messages\": [{\"role\": \"user\", \"content\": $(echo "$*" | jq -Rs .)}]
    }" | jq -r '.content[0].text'
}

# Usage:
ask "What does the sticky bit do on a directory?"
ask "Give me a one-liner to find files modified in the last 24 hours"

⚙️ Configuration — ~/.curlrc

curl reads ~/.curlrc on every invocation. A sensible baseline for SysAdmin use:

# ~/.curlrc

# Always follow redirects
location

# Show errors even in silent mode (use -s safely)
show-error

# Set a reasonable timeout (seconds)
max-time = 30

# Retry on transient failures
retry = 3
retry-delay = 2

# Compressed responses (saves bandwidth)
compressed

# Use a descriptive User-Agent
user-agent = "sysadmin-curl/1.0"
Do not set -k (insecure) in ~/.curlrc. It is tempting for internal servers with self-signed certs, but it silently disables certificate verification for every curl call, including ones to external services. Use -k explicitly on the command line when you need it.
The show-error option in ~/.curlrc means you can safely use -s in scripts without losing error messages. Silent mode suppresses the progress meter; errors still surface. This is the right default for scripting.

📊 Honest Assessment

The Good

The Friction

Bottom Line

curl is not a new tool and it is not glamorous. What it is, is universal, reliable, and already present. For SysAdmins who want to interact with AI APIs from the shell without introducing new dependencies, curl is the correct starting point.

The mental model shift is simple: AI APIs are REST APIs. You already know how to call REST APIs with curl. The API key goes in a header. The prompt goes in a JSON body. The response comes back as JSON. Parse it with jq. That's the whole pattern.

Master the jq -Rs . trick, store your API key in the environment, and write one reusable shell function. You now have AI access from every terminal on every server where you have a shell.

🔗 Related Tools in This Section