Part 3 got your Mac configured from scratch: Homebrew, your shell, your terminal, your core tools. The machine is functional. But functional isn't the same as ready. Before you clone a single repository, push a single commit, or connect to a single remote server, you need to establish who you are on this machine and prove it in a way that's consistent, secure, and repeatable. That's what Part 4 is about.
This isn't ceremony. Git identity, SSH keys, GitHub authentication, and secrets hygiene are the foundation everything else sits on. Skip them, or rush through them, and you spend the next six months cleaning up attribution errors, rotating leaked credentials, and explaining to your team why your commits show up under a stranger's email address. The configuration takes about thirty minutes. The mistakes it prevents can take days to untangle.
Why Identity and Authentication Come First
The cost of skipping this step
Most developers set up authentication exactly once, on their first machine, years ago, and never think about it again until something breaks. A new machine is the moment that pattern catches up with you. The wrong Git email means your commits don't appear on your GitHub contribution graph. Worse, if you're working across personal and professional accounts, a mismatched email can attribute your employer's code to your personal account or expose your work email in a public repository's commit history. Neither is a good situation.
Leaked API keys are a different category of problem entirely. A single .env file committed to a public repository can expose database credentials, payment processor keys, or cloud provider tokens within minutes. Automated bots scan GitHub for secrets constantly. The average time between a secret being pushed and being discovered by a malicious actor is measured in seconds, not hours.
Private key loss is quieter but just as damaging. If you generate an SSH key, never back up the passphrase or store the key securely, and then lose the machine, you're locked out of every server you added that key to. Recovery means touching every remote host individually.
What this part covers
This part covers four things in sequence. First, configuring your Git identity so every commit carries the right name and email. Second, authenticating with GitHub CLI so your local machine can push, pull, and interact with GitHub without re-entering credentials. Third, generating and managing an SSH key pair using the ed25519 algorithm with macOS Keychain integration. Fourth, building a clean SSH config file that makes remote connections readable and repeatable.
By the end, you'll have a fully authenticated, secure developer identity on your machine. Part 5 will build on this foundation when we move into dotfiles and environment configuration, but none of that works cleanly without what you're setting up here.
Configuring Your Git Identity
Setting name, email, and default branch
Git's global configuration is stored in ~/.gitconfig and applies to every repository on your machine unless a repo-level config overrides it. The first thing to set is your name and email. Open your terminal and run:
git config --global user.name "Your Name"
git config --global user.email "[email protected]"The email here is not cosmetic. It must match exactly the email address associated with your GitHub account. GitHub uses this value to link commits to your profile, populate your contribution graph, and, if you later configure commit signing, to verify your identity. A single character difference means your commits show up as unlinked gray boxes instead of attributed contributions.
While you're here, set your default branch name:
git config --global init.defaultBranch mainGit still defaults to master on a fresh install. Every new repository you initialize will use main instead, which aligns with current GitHub conventions and avoids the awkward rename step after the fact.
Personal vs. work email
If this machine is for work, use your work email. If it's personal, use your personal email. Mixing them is the most common Git identity mistake developers make on new machines. Your commits are permanent and public. The email embedded in them is too.
Choosing a pull strategy
Set your pull strategy explicitly so Git doesn't warn you every time you run git pull:
git config --global pull.rebase falseThis tells Git to use a merge commit when pulling remote changes rather than rebasing your local commits on top of them. For most developers, especially those working on shared branches or coming back to Git after a break, merge-based pulls are easier to reason about. The history is messier but the behavior is predictable. You can always change this per-repository once you have a stronger opinion about it.
Verifying your global config
Before you touch any repository, confirm everything looks right:
git config --global --listYou should see your name, email, default branch, and pull strategy all listed. If anything is missing or wrong, rerun the relevant config command. The ~/.gitconfig file is plain text and can also be opened directly in any editor if you prefer to review or adjust it that way.
Authenticating with GitHub CLI
Installing and running gh auth login
If you followed Part 2 of this series, GitHub CLI (gh) is already installed via Homebrew. Confirm it's available:
gh --versionIf the command returns a version number, you're ready. If not, run brew install gh before continuing. With gh confirmed, start the authentication flow:
gh auth loginThe CLI will walk you through a short series of prompts. The first asks which account type you're connecting to. Choose GitHub.com unless you're working against a GitHub Enterprise instance.
HTTPS vs SSH: which to choose
The next prompt asks which protocol you want to use for Git operations: HTTPS or SSH. Both work. The practical difference is where authentication happens.
HTTPS uses a token stored by a credential helper. It's simpler to set up and works everywhere, including environments where SSH ports are blocked. SSH uses a key pair you control entirely, which is more portable across machines if you manage your keys well. For most developers on a personal or work Mac, HTTPS is the easier default. If you're already planning to set up SSH keys for remote server access (which this part covers next), you can choose SSH here and use the same key pair for GitHub too.
Browser-based login flow
After choosing your protocol, gh will ask how you want to authenticate. Choose Login with a web browser. The CLI generates a one-time device code and opens your browser to github.com/login/device. Paste the code, confirm the authorization, and GitHub sends the token back to your terminal. The whole flow takes under a minute.
Verifying authentication
Once the flow completes, confirm everything worked:
gh auth statusYou should see your GitHub username, the authentication method, and the token scopes granted. The default scopes cover repository access, Gist creation, and read access to your profile. That's sufficient for the vast majority of development workflows. A useful side effect of gh auth login is that it also configures Git's credential helper automatically, so git push and git pull work without prompting you for credentials every time.
Generating and Managing Your SSH Key
Generating an ed25519 key pair
RSA was the standard SSH key algorithm for a long time. ed25519 is better. It produces smaller keys, signs faster, and offers equivalent security with a shorter key length. There's no practical reason to generate an RSA key on a new machine today.
Generate your key pair with:
ssh-keygen -t ed25519 -C "[email protected]"The -C flag sets a comment on the key. Using your email here makes it easy to identify which key belongs to which account when you're looking at an authorized_keys file on a remote server. When prompted for a file location, press Enter to accept the default (~/.ssh/id_ed25519). When prompted for a passphrase, set one. Always set one.
Starting the SSH agent
The passphrase protects your private key at rest. The SSH agent holds your decrypted key in memory so you don't have to type the passphrase every time you open a connection. Start the agent in your current shell session:
eval $(ssh-agent -s)This starts the agent process and sets the environment variables your shell needs to communicate with it. You'll see an agent PID printed to confirm it's running.
Never share your private key
The file at ~/.ssh/id_ed25519 is your private key. It never leaves your machine. Don't copy it to cloud notes, don't paste it into a chat, don't commit it to a repository. If it's ever exposed, delete it and generate a new pair immediately.
Adding your key to the macOS Keychain
Add your key to the agent with macOS Keychain integration so the passphrase persists across reboots:
ssh-add --apple-use-keychain ~/.ssh/id_ed25519The --apple-use-keychain flag stores your passphrase in the macOS Keychain. The next time your machine restarts and the SSH agent loads, it retrieves the passphrase automatically. You get the security of a passphrase-protected key without the friction of typing it every session.
Your public key (~/.ssh/id_ed25519.pub) is safe to share. It's designed to be shared. Copy it to your clipboard with:
pbcopy < ~/.ssh/id_ed25519.pubFrom there, paste it into GitHub under Settings > SSH and GPG Keys, or into the authorized_keys file on any remote server you need to access.
Creating a Clean SSH Config File
Why SSH aliases matter
The raw SSH command for connecting to a remote development server looks something like this:
ssh [email protected] -i ~/.ssh/id_ed25519That's fine once. It's tedious every time, and it's easy to mistype a hostname or forget which key goes with which server. The SSH config file at ~/.ssh/config solves this by letting you define named host aliases with all the connection parameters baked in.
The file doesn't exist by default on a fresh Mac. Create it:
touch ~/.ssh/configSetting correct file permissions
SSH is strict about file permissions. If your config file or the .ssh directory itself has permissions that are too open, SSH will refuse to use the config entirely and print a warning. Set the permissions correctly before writing anything:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/config700 on the directory means only your user can read, write, or execute it. 600 on the config file means only your user can read or write it. Other users on the system get nothing. SSH enforces these requirements as a security guarantee, not a suggestion.
Writing your first SSH host alias
Open ~/.ssh/config in your editor and add a host block:
1Host remote-host-01
2 HostName remote-host-01.example.internal
3 User devuser
4 IdentityFile ~/.ssh/id_ed25519
5 AddKeysToAgent yes
6 UseKeychain yesEach line does something specific. Host sets the alias you type in the terminal. HostName is the actual address SSH connects to. User sets the remote username so you don't have to include it in the command. IdentityFile points to the private key to use. AddKeysToAgent yes loads the key into the SSH agent automatically on first use. UseKeychain yes pulls the passphrase from macOS Keychain so you're never prompted.
With this block in place, ssh remote-host-01 replaces the full command entirely. You can add as many Host blocks as you need, one per server, environment, or service.
If you chose SSH over HTTPS when authenticating with GitHub CLI, add this block as well:
1Host github.com
2 HostName github.com
3 User git
4 IdentityFile ~/.ssh/id_ed25519
5 AddKeysToAgent yes
6 UseKeychain yesThis ensures every Git operation targeting GitHub uses your ed25519 key automatically, without any additional flags or configuration at the repository level.
Part 5 takes everything you've established here and builds your dotfiles system on top of it: a portable, version-controlled configuration layer that makes any new Mac feel like home in under an hour.
Secrets Management: Keeping Credentials Out of the Wrong Places
Part 3 covered SSH key generation, agent configuration, and the ~/.ssh/config file that makes remote connections predictable. Now the focus shifts to something that trips up developers at every experience level: keeping credentials away from places they should never be.
A secret is anything that grants access or proves identity. API keys, OAuth tokens, database passwords, private keys, webhook secrets. If someone else gets it, they get what it unlocks. That's the whole definition.
Where Secrets Must Never Live
The list of wrong places is longer than most developers expect.
Git history is the most dangerous. A secret committed even once is permanently embedded in every clone of that repository, even after deletion from the working tree. Screenshots get synced to cloud photo libraries and indexed by services you don't control. Notes apps like Apple Notes or Notion often sync to mobile devices and share data with third-party servers. Shell history stores every command you've typed, including any export SECRET=abc123 you ran inline. Committed config files like config.json or settings.py with hardcoded credentials get pushed to remotes and shared with every contributor.
The History Problem
Deleting a file doesn't remove it from Git history. Every clone of your repository still contains every version of every file ever committed. Rotation is the only real fix.
The .env Pattern: Real Files vs Example Files
The .env pattern solves the configuration problem cleanly. You commit a .env.example file with placeholder values so collaborators know what variables the project needs. You keep the real .env ignored from version control entirely.
1# .env.example
2DATABASE_URL=postgres://user:password@localhost:5432/mydb
3STRIPE_SECRET_KEY=sk_live_REPLACE_ME
4WEBHOOK_SECRET=your_webhook_secret_here1# .env (never committed)
2DATABASE_URL=postgres://forop:actualpassword@localhost:5432/proddb
3STRIPE_SECRET_KEY=sk_live_abc123realkey
4WEBHOOK_SECRET=wh_sec_actualvalue"The .env.example is a contract. The .env is the implementation. Only one of them belongs in version control."
Using a Password Manager CLI for Runtime Injection
The .env pattern still writes secrets to disk. For higher-sensitivity credentials, you can skip the file entirely using a password manager CLI to inject secrets at runtime.
1Password's CLI makes this clean:
export STRIPE_SECRET_KEY=$(op read "op://Personal/Stripe API/credential")That command reads the secret from your 1Password vault and assigns it to the environment variable in memory. Nothing touches a file on disk.
Bitwarden, Doppler, and AWS Secrets Manager all offer equivalent CLI patterns. The approach is the same regardless of which tool you use: the secret lives in the vault, gets injected at process start, and disappears when the session ends.
Runtime Injection Principle
Secrets injected at runtime through a password manager CLI never exist as plaintext files. They live in memory for the duration of the process and nowhere else.
Keeping Secrets Out of Git
.gitignore is your first line of defense, and it needs to be configured before the first commit on any new project. Not after. Before.
Configuring .gitignore Correctly
Add secret-bearing files to .gitignore at project initialization:
1# .gitignore
2.env
3.env.local
4.env.production
5*.pem
6*.key
7.DS_StoreYou should also configure a global ignore file that applies across every repository on your machine:
git config --global core.excludesfile ~/.gitignore_globalThen add your most common patterns to ~/.gitignore_global. .DS_Store, .env, *.pem, and *.key belong there. This catches the cases where you forget to create a project-level .gitignore before the first commit.
What to Do If a Secret Is Already Committed
Rotate the credential first. Always. Before you touch Git history, before you open a terminal, before you do anything else: go to the service that issued the secret and revoke it. Generate a new one. The old credential is compromised the moment it touches a commit, and cleaning history doesn't undo any access that may have already occurred.
After rotation, you can rewrite history using git filter-repo or BFG Repo Cleaner. Both tools remove a file or string from every commit in the repository's history. BFG is faster for simple cases. git filter-repo is more flexible and actively maintained.
Secret Scanning Tools
GitHub's built-in secret scanning checks pushed commits against known secret patterns and alerts you when a match is found. It's enabled by default on public repositories and configurable on private ones.
For local prevention, detect-secrets and gitleaks both work as pre-commit hooks, scanning staged files before a commit completes. A failed scan blocks the commit until you resolve the issue.
No tool replaces the habit of running git diff --staged before every commit. Scanning tools catch patterns they recognize. Your own eyes catch the things that don't match a known pattern but still shouldn't be in source control.
Setting Up Remote Development Workflows
SSH Aliases as the Foundation
Every remote workflow in this section builds on the ~/.ssh/config aliases from Part 3. When you type ssh remote-host-01 instead of ssh -i ~/.ssh/id_ed25519 -p 2222 [email protected], you've already eliminated the most common source of remote workflow friction. The config file does the work once so you don't repeat it every session.
Persistent Sessions with tmux
The problem with a plain SSH connection is that it dies when your network drops, your laptop sleeps, or you close the terminal. Everything running in that session disappears with it.
tmux solves this by running a persistent session on the remote server that exists independently of your connection. You attach to it, detach from it, and reconnect to it from anywhere.
Start a named session after connecting:
ssh remote-host-01
tmux new -s devWhen you disconnect or close the window, the session keeps running. Reconnect from a new terminal:
ssh remote-host-01
tmux attach -t devEverything is exactly where you left it. The process you were running, the directory you were in, the output on screen. Nothing lost.
Remote Logs and Project Directories
Keep remote project directories consistent. ~/projects/ as the root for all repositories means you always know where to navigate. ~/projects/api/, ~/projects/frontend/, ~/projects/scripts/. Predictable structure matters more on remote machines than local ones because you're navigating without a file browser.
For quick log inspection without opening a full session, SSH accepts a command directly:
ssh remote-host-01 tail -f /var/log/app.logThat one-liner opens a connection, runs the command, and streams output to your local terminal. No tmux session required for short-lived tasks.
Avoiding Raw SSH Command Sprawl
Raw SSH commands with multiple flags become unmanageable fast. A command like ssh -i ~/.ssh/deploy_key -p 2222 -L 5432:localhost:5432 [email protected] is correct once and wrong every other time you mistype a flag. The ~/.ssh/config alias collapses that entire command into ssh remote-host-01. For developers who prefer not to live in the terminal for editing, the VS Code Remote SSH extension connects to any host alias from your config and opens a full editor session on the remote machine. The underlying mechanism is identical. The interface is just different.
Common Mistakes and How to Avoid Them
Identity and Key Mistakes
Wrong Git email is more consequential than it looks. Commits attributed to an unknown email address don't appear in your GitHub contribution graph, can break GPG commit signing, and create confusion in code review when nobody can tell who authored a change. Verify with git config --global --list immediately after setup, and verify again after any machine migration.
Losing your private key with no backup strategy means losing access to every server and service that trusted it. Generate keys with a passphrase so a backup stored in a password manager is encrypted at rest. Storing the key only on one machine is one hardware failure away from a complete lockout.
Copying private keys into Slack, email, or cloud notes is the equivalent of photographing your house key and posting it publicly. The key is the credential. Treat it accordingly.
Using the same SSH key across every machine means a single compromised device compromises every service you've ever authorized. Generate a unique key per device. Name them clearly in your ~/.ssh/config.
Secrets and Configuration Mistakes
The Hardcoding Trap
Hardcoding an API key directly in a source file feels like a shortcut. It becomes a liability the moment that file is committed, shared, or read over someone's shoulder.
Forgetting to add .env to .gitignore before the first commit is the single most common way secrets end up in Git history. Add it before you write a single line of application code.
Skipping the passphrase on SSH keys because unlocking feels inconvenient is a false tradeoff. macOS Keychain stores the passphrase after the first unlock and handles every subsequent authentication silently. The friction is a one-time cost. The protection is permanent.
Not verifying git config after setup means you might push dozens of commits under the wrong identity before noticing. Run git config --global --list and confirm the output before your first commit on any new machine.
Part 4 Checklist: Your Secure Developer Identity
The foundation is set. Identity, keys, secrets hygiene, and remote session management are all in place. Part 5 moves into terminal configuration and shell tooling: building a zsh environment that's fast, readable, and consistent across every machine you work on.