SSH and the VS Code workflow
Almost everything in this guide assumes you can SSH into the partition. SSH on IBM i is solid; it’s been there for years; and once it’s set up, the VS Code workflow it enables is by far the best development experience for IBM i in 2026 — better than Qp2term, better than RDi for most tasks (RPG-debugging excepted), better than 5250 for anything that touches the IFS.
This chapter gets you from “SSH might be running” to “I can edit, browse, and run things from VS Code over SSH against the partition.”
1. Install and start the SSH server
openssh ships in the IBM base repo:
yum install openssh
Then start the SSHD server from CL or QSH:
STRTCPSVR *SSHD
To make SSHD start automatically when TCP/IP starts (the normal case after IPL), set the autostart attribute:
CHGTCPSVR SVRSPCVAL(*SSHD) AUTOSTART(*YES)
Verify:
NETSTAT *CNN
Look for a *Listen entry on local port 22. If it’s there, the daemon is up. From your workstation:
ssh jesse@your-ibmi.example.com
If you land at a bash-5.x$ prompt, you’re done with the basics.
The SSH server reads
/QOpenSys/etc/ssh/sshd_config. Default settings are reasonable. The two changes worth making early:PasswordAuthentication no(after you’ve set up keys), andPermitRootLogin no(which on IBM i means: refuse SSH forQSECOFR, the closest analogue toroot).
2. Set up SSH keys
Password authentication works but you don’t want to type your IBM i password into VS Code thirty times an hour. Generate a key on your workstation:
ssh-keygen -t ed25519 -C "jesse@workstation"
Then copy the public key to the IBM i:
ssh-copy-id jesse@your-ibmi.example.com
If ssh-copy-id isn’t available on your workstation (Windows without WSL, for example), copy it manually:
# On the IBM i, in PASE:
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "ssh-ed25519 AAAA... jesse@workstation" >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
The chmod matters. SSHD enforces strict permissions: if ~/.ssh is group- or world-writable, or if authorized_keys is, SSHD silently refuses to use the file and falls back to password auth.
A common failure mode: a user’s
HOMEDIRis set to/home/jesse, but/home/jessedoesn’t exist or has wrong ownership. Confirm withDSPUSRPRFfrom CL, then in PASE:ls -lad /home/jesse /home/jesse/.ssh /home/jesse/.ssh/authorized_keysThe directory and the file should be owned by
jesse(the IBM i user), notqsecofr, notqhttpsvr, notroot.
3. Set $HOME and a real shell
Every user that will SSH in needs:
- A valid
HOMEDIRset on the user profile. - A real home directory in the IFS, owned by them.
- A shell that exists. PASE bash lives at
/QOpenSys/pkgs/bin/bash.
From CL:
CHGUSRPRF USRPRF(JESSE) HOMEDIR('/home/jesse')
From PASE (as a user with authority — qsecofr or somebody with *ALLOBJ):
mkdir -p /home/jesse
chown jesse /home/jesse
chmod 700 /home/jesse
To set bash as the default login shell, drop a .profile into the home directory that exec’s bash:
# /home/jesse/.profile
if [ -x /QOpenSys/pkgs/bin/bash ] && [ -z "$BASH_VERSION" ]; then
export PATH=/QOpenSys/pkgs/bin:$PATH
exec bash -l
fi
The exec bash -l re-launches bash as a login shell, which then reads /etc/profile, ~/.bash_profile, and ~/.bashrc the way users expect.
If you’d rather use
ksh(the IBM i default and whatchshwould point you at), that’s fine — it’s a perfectly capable shell. But every modern tutorial assumes bash, and manycomposerandnpmpost-install scripts have bashisms that ksh doesn’t handle. Save yourself the friction; use bash.
4. Terminal CCSID
When you SSH in, your terminal session has a CCSID. Get this wrong and PASE will mangle every non-ASCII character you type or read.
The combination that works for almost everyone in 2026:
- PASE side:
PASE_LANG=EN_US.UTF-8, set in/QOpenSys/etc/profileor your~/.profile. - Job side:
JOBCCSID=1208(UTF-8) for the SSH job, set via the user profile or job description. - Your terminal emulator: Set to UTF-8 on the workstation side (the default for almost every modern terminal).
Putting that into a per-user profile fragment:
# ~/.profile or ~/.bash_profile
export PASE_LANG=EN_US.UTF-8
export LC_ALL=EN_US.UTF-8
export LANG=EN_US.UTF-8
export PATH=/QOpenSys/pkgs/bin:$PATH
The JOBCCSID part needs CL. Set it on the user profile so every SSH job from this user starts with it:
CHGUSRPRF USRPRF(JESSE) CCSID(1208)
See CCSID sanity for the full picture of why these three settings have to agree.
5. Code for IBM i (VS Code extension)
The Halcyon/IBM-blessed VS Code extension is Code for IBM i, available in the VS Code marketplace as IBM.code-for-ibmi. Install it. Then:
- Add a connection — sidebar icon, “Connect to a remote IBM i”.
- Authenticate — username, and either a password or (recommended) an SSH key file.
- Connect. The extension probes the partition, finds
yum, and offers to install any helpers it needs.
Once connected, you get:
- A PASE filesystem browser rooted wherever you point it (typically
/home/<user>and/QOpenSys). - A library / object browser for IFS-resident objects and library list contents.
- An integrated terminal that opens a PASE shell inside VS Code.
- Source member editing for QSYS source physical files (the older RPG/CL style) — VS Code edits them like normal files; the extension handles the conversion.
- Compile commands wired to right-click context menus.
For this guide’s purposes, the two features that matter most are the PASE filesystem browser and the integrated terminal. Once you have those, VS Code becomes the natural editor for everything PHP, Python, Node, and shell-script-shaped on the partition.
Code for IBM i works over SSH. It uses the same SSH connection you set up above, plus an internal SQL connection over the same SSH tunnel for some of the database-aware features. It doesn’t need anything extra on the IBM i beyond
opensshand (for some features)python3-itoolkit.
6. The actual day-to-day workflow
Once it’s all set up, the loop looks like this:
- Open VS Code on your workstation. The Code for IBM i sidebar shows your saved connections.
- Click the connection. It opens a remote workspace; the VS Code window’s color/title indicates remote mode (just like SSH-remote VS Code).
- Navigate to a project folder under
/home/jesse/projects/something/. Open files, edit, save. Saves write directly to the IBM i over SSH. - Open a terminal (Ctrl-
in VS Code). You're at a PASE shell on the IBM i. Runcomposer install,npm test,python -m pytest,git status` — whatever the project needs. - For RPG, use the source member browser to open library-list-resident source files. The extension handles compile through right-click → “Compile”.
That’s the loop. It’s the same loop you’d have on Linux or macOS with VS Code’s Remote-SSH extension, just specialized for IBM i.
7. The things that bite people on day one
/dev/null permissions. On rare older systems, /QOpenSys/dev/null ends up with weird authority and SSHD-spawned shells fail in confusing ways. If your bash session immediately dies after login, ls -la /dev/null and confirm it’s crw-rw-rw-. Fix with chmod 666 /dev/null and restart SSHD.
HOMEDIR not actually a directory. DSPUSRPRF says /home/jesse, but /home/jesse doesn’t exist. SSHD logs you in, drops you somewhere, and cd ~ goes nowhere reasonable. Always mkdir -p and chown after CHGUSRPRF.
Profile inherited from QSECOFR. If you copied a user from QSECOFR, the new user inherits JOBCCSID(*USRPRF) chained back to QSYSVAL 65535. Set CCSID on the new user explicitly — CHGUSRPRF USRPRF(JESSE) CCSID(1208).
SSHD running, but iptables/firewall in front. If STRTCPSVR *SSHD reports success but ssh from your workstation hangs or refuses, the partition is up but a firewall in front of it isn’t letting port 22 through. This is a network-team conversation; NETSTAT *CNN on the IBM i confirms the daemon is listening.
Two SSH servers. Some shops install both IBM’s OpenSSH and a third-party SSH and end up with one trying to bind to port 22 while the other is already running. WRKACTJOB SBS(QUSRWRK) — look for one and only one SSHD job.
Where next
SSH works, VS Code is connected, and your terminal speaks UTF-8. The next chapter, CCSID sanity, is the one that prevents 80% of “this character looks wrong” bug reports. It’s worth reading even if everything seems fine.