Setting up USS on z/OS
July 06, 2026
This guide covers the first-time setup for a correct USS environment on z/OS with zopen. By the end of this guide, you'll be able to ssh into USS, land in a bash shell with zopen tools on your PATH, and run standard UNIX tools like git.
Create Home Directory
The first thing we need is a home directory for our user on USS, which will contain our configuration files and programs installed with zopen. Create a new ZFS file system and mount it to your desired location on USS, typically /home/<username> (e.g., /home/griffin). The ZFS directory should be at least 3 GB in size and set as your OMVS user's home in RACF, e.g. ALU griffin OMVS(HOME('/home/griffin'))
Install zopen
zopen is a package manager which is used to install open source software on z/OS USS.
Install zopen from https://zopen.community/Guides/QuickStart. Follow the instructions for "Manual Installation."
After installing zopen, install the following essential packages:
zopen install bash coreutils curl jq make ncurses git
You will also need a text editor of your choice, such as micro, nano or vim. A list of available editors on z/OS can be found on the zopen website.
[!TIP] You can edit USS files from TSO using the
OEDITprogram, which is useful if you're more comfortable with the ISPF editor, or if you accidentally break your configuration and can't connect via SSH.
Configure Bash
Let us pause for a moment and talk about shells. A shell is another word for a command-line interpreter, a program that parses input text as commands to be executed. In standard z/OS, this is TSO. USS comes with its own UNIX-style shell, the z/OS shell, located at /bin/sh. When you log in to USS, you are using this z/OS shell. In the following section, I will show you how to configure an alternate shell, the Bash shell. I recommend using bash instead of the default z/OS shell because it's the most widely used UNIX shell, and thus scripts shared with us by other people, examples found online, and instructions from documentation will almost always assume bash.
In UNIX shells, logon scripts are files containing commands that are executed whenever a user logs in. These are analogous to a CLIST or REXX exec invoked at login on TSO. Most z/OS systems ship with a system-supplied logon script at /etc/profile, which is executed whenever a user logs in to USS. I highly recommend that you do not modify this file. To use Bash, we will create a logon script that starts bash after your user logs on. This means that the /etc/profile logon script will be executed, followed by your custom logon scripts which we will create below. Your user will inherit the system supplied configuration options, in addition to the configuration options specified in your user logon scripts. If a later script (such as .bashrc) sets a variable that an earlier script (such as /etc/profile) already defined, the later value wins.
With this setup, the chain of actions when logging in to USS will look like this:
- User logs in to USS, starting a z/OS shell session
- The z/OS shell session sources
/etc/profile, inheriting the system-supplied configuration - The z/OS shell then sources
.profile, which makes the zopen tools (including Bash) available, and starts Bash as a login session. Now we are in the Bash shell's logon procedure. - The Bash shell sources
/etc/profile, just like the z/OS shell did. - Bash shell sources
.bash_profilewhich in turn sources.bashrcNow we are running in a Bash shell with both the system-supplied and our own custom options.
File tags and encodings
The next subject we need to tackle before creating any files is character encodings, the numeric format used to represent text. The default character encoding scheme on z/OS, including USS, is EBCDIC. However, UNIX programs like git, node, and python typically require an ASCII-based character encoding. To keep track of different encodings, z/OS uses a numeric code called Coded Character Set Identifier (CCSID), with different values corresponding to different character encodings. For example, 1047 is EBCDIC, 819 is ISO 8859-1 (Latin-1), and 1208 is UTF-8. There are also special values for untagged/unknown, and binary files. USS files have a special metadata field called the file tag which contains their associated CCSID. Programs also have a CCSID, which declares the encoding they expect text to be in.
For our files to be correctly processed, they must be correctly encoded and tagged. Depending on your system's default configuration (what is set in /etc/profile) the files you create may be ASCII or EBCDIC, and may or may not be tagged. Since our Bash setup will automatically convert between both encodings (we will discuss this in more detail later), the encoding used for your logon scripts is not that important. However, they must be tagged correctly, since z/OS needs to know what encoding a file is in to convert it. Additionally, .profile must be EBCDIC because it is executed by the z/OS shell, which may or may not (depending on your system-supplied configuration) be able to do the auto converting.
A file's tag can be checked with chtag -p, and changed with chtag -tc, while the file itself can be re-encoded with iconv. File contents can be inspected directly with od which opens files in binary mode (bypassing conversion).
To illustrate, I will show a few examples:
griffin@STLAB57:~$ printf '\x88\x85\x93\x93\x96\x15' > demo.txt
griffin@STLAB57:~$ od -An -tx1 demo.txt
88 85 93 93 96 15
griffin@STLAB57:~$ chtag -p demo.txt
t ISO8859-1 T=on demo.txt
griffin@STLAB57:~$ cat demo.txt
�����griffin@STLAB57:~$ chtag -tc IBM-1047 demo.txt
griffin@STLAB57:~$ cat demo.txt
hello
griffin@STLAB57:~$ od -An -tx1 demo.txt
88 85 93 93 96 15
First, I write "hello\n" in IBM-1047 as raw bytes to demo.txt. Since I have the environment variable _TAG_REDIR_OUT=txt (more on this later) it is incorrectly tagged as ISO8859-1, so attempting to print it produces mojibake. By changing the tag to match the actual encoding, the system knows how to read the file correctly. Note that only the tag was modified, not the underlying data.
For another example, suppose I accidentally created .profile as ASCII. Because .profile is executed before our automatic file conversion options are enabled, it must be in IBM-1047 format.
griffin@STLAB57:~$ chtag -p .profile
t ISO8859-1 T=on .profile
griffin@STLAB57:~$ od -An -tx1 .profile | head
23 20 2f 68 6f 6d 65 2f 67 72 69 66 66 69 6e 2f
2e 70 72 6f 66 69 6c 65 0a 0a 23 20 4a 75 73 74
20 65 6e 6f 75 67 68 20 74 6f 20 6c 6f 63 61 74
65 20 62 61 73 68 2c 20 74 68 65 6e 20 62 65 63
6f 6d 65 20 62 61 73 68 2e 0a 0a 65 78 70 6f 72
74 20 5f 42 50 58 4b 5f 41 55 54 4f 43 56 54 3d
4f 4e 0a 5b 20 2d 65 20 22 24 7b 48 4f 4d 45 7d
2f 7a 6f 70 65 6e 2f 65 74 63 2f 7a 6f 70 65 6e
2d 63 6f 6e 66 69 67 22 20 5d 20 26 26 20 2e 20
22 24 7b 48 4f 4d 45 7d 2f 7a 6f 70 65 6e 2f 65
griffin@STLAB57:~$ cp .profile .profile.tmp
griffin@STLAB57:~$ chtag -r .profile.tmp
griffin@STLAB57:~$ iconv -f ISO8859-1 -t IBM-1047 .profile.tmp > .profile.new
griffin@STLAB57:~$ chtag -p .profile.new
t ISO8859-1 T=on .profile.new
griffin@STLAB57:~$ chtag -tc IBM-1047 .profile.new
griffin@STLAB57:~$ mv .profile.new .profile
griffin@STLAB57:~$ rm .profile.tmp
griffin@STLAB57:~$ chtag -p .profile
t IBM-1047 T=on .profile
griffin@STLAB57:~$ od -An -tx1 .profile | head
7b 40 61 88 96 94 85 61 87 99 89 86 86 89 95 61
4b 97 99 96 86 89 93 85 15 15 7b 40 d1 a4 a2 a3
40 85 95 96 a4 87 88 40 a3 96 40 93 96 83 81 a3
85 40 82 81 a2 88 6b 40 a3 88 85 95 40 82 85 83
96 94 85 40 82 81 a2 88 4b 15 15 85 a7 97 96 99
a3 40 6d c2 d7 e7 d2 6d c1 e4 e3 d6 c3 e5 e3 7e
d6 d5 15 ad 40 60 85 40 7f 5b c0 c8 d6 d4 c5 d0
61 a9 96 97 85 95 61 85 a3 83 61 a9 96 97 85 95
60 83 96 95 86 89 87 7f 40 bd 40 50 50 40 4b 40
7f 5b c0 c8 d6 d4 c5 d0 61 a9 96 97 85 95 61 85
Removing the tag (chtag -r) ensures that no autoconversion settings change the bytes on read, so iconv gets the file's actual content regardless of your configuration. Note that after running iconv, .profile.new is encoded as EBCDIC, but tagged ASCII, because I have the _TAG_REDIR_OUT=txt variable. It is left to the programmer to tell the system which tag is correct for a file.
.profile
The first file we will create is .profile. This file will be sourced by the z/OS shell after /etc/profile, and will start Bash.
# Just enough to locate bash, then become bash.
export _BPXK_AUTOCVT=ON
[ -e "${HOME}/zopen/etc/zopen-config" ] && . "${HOME}/zopen/etc/zopen-config"
if [ -z "$BASH_VERSION" ] && command -v bash >/dev/null 2>&1; then
exec bash -l
fi
This script does three things: First, it enables the _BPXK_AUTOCVT feature which allows EBCDIC-based programs (in this case /bin/sh) to read ASCII-encoded files (in this case zopen-config) and vice versa (more on this later). Second, it sources the zopen-config, making zopen programs available in the z/OS shell environment. This is necessary because we installed Bash with zopen. Third, it starts bash as a login shell.
.bash_profile
[ -r ~/.bashrc ] && . ~/.bashrc
Depending on the mode in which Bash was started, the program will source either .bash_profile or .bashrc. This behavior serves only to make our lives more difficult, so we will make .bash_profile source .bashrc when run, making .bashrc the authoritative source of all our configuration options.
.bashrc
.bashrc is the heart of your configuration. I will break this file down into sections, each with their own purpose.
USS File Tagging and Conversion
To make our system work, we'll need to set a few special environment variables related to file tags:
export _BPXK_AUTOCVT=ON
export _CEE_RUNOPTS="FILETAG(AUTOCVT,AUTOTAG) POSIX(ON)"
export _TAG_REDIR_ERR=txt
export _TAG_REDIR_IN=txt
export _TAG_REDIR_OUT=txt
_BPXK_AUTOCVT=ON enables automatic conversion of tagged files between ISO 8859-1 and EBCDIC. When a file is tagged with a CCSID, the kernel will automatically convert between the file's tag and the program's CCSID on read/write, meaning that an ASCII program can read an EBCDIC file, and vice versa.
_CEE_RUNOPTS="FILETAG(AUTOCVT,AUTOTAG) POSIX(ON)" sets several Language Environment options for C and C++ programs:
AUTOCVTenables autoconversion in the C runtime, which is necessary because the language environment is unaffected by_BPXK_AUTOCVT=ONAUTOTAGmakes the LE runtime tag new files with the program's CCSID when they are created.POSIX(ON)makes programs running in USS behave like standard UNIX processes, rather than MVS/batch programs.
_TAG_REDIR_OUT=txt, _TAG_REDIR_IN=txt, _TAG_REDIR_ERR=txt ensures that files created by shell redirection (cat foo > out.txt) use the same encoding as the program that created them.
For more information on these environment variables, and other special environment variables in z/OS, see Commonly used environment variables.
zopen
Our next step is ensuring the zopen-config file is sourced by Bash, so our zopen tools will be available:
# zopen tool config
if [ -e "${HOME}/zopen/etc/zopen-config" ]; then
. "${HOME}/zopen/etc/zopen-config" >/dev/null
fi
This code allows you to use zopen tools. When sourcing zopen-config, it emits a warning ("NOTE: Conflicting tools (eg. man, cat, grep, make) will take precedence over z/OS /bin tools. Pass the option --nooverride-zos-tools to avoid this.") so on its second invocation in .bashrc we discard the standard output with >/dev/null. Errors will still be printed, but the note about conflicting tools will only be printed once.
PATH variable
PATH is an environment variable which contains a list of directories where executable programs are located. In order to run a program from the command line in USS, the directory containing that program needs to be in your PATH.
# Software install locations, to be added to the PATH
export JAVA_HOME=/java21/J21.0_64
export NODE_HOME=/sdkV24/usr/lpp/IBM/node/v24r0/IBM/node-latest-os390-s390x
export PYTHON_HOME=/pyth314/usr/lpp/IBM/python/v3r14/pyz
export CPP_HOME=/xlc220/usr/lpp/IBM/cnw/v2r2/openxl
PATH="${PATH%:}" # fix trailing ':' from zopen-config
PATH="$PATH:$JAVA_HOME/bin"
PATH="$PATH:$NODE_HOME/bin"
PATH="$PATH:$PYTHON_HOME/bin"
PATH="$PATH:$CPP_HOME/bin"
export PATH
This is the PATH section from my configuration, which adds Java 21, Node.js 24, Python 3.14, and OpenXL C/C++ 2.2 to my PATH environment variable. The line PATH="${PATH%:}" is necessary because of a bug in zopen which causes a colon to be appended to the PATH after it is sourced. The locations shown above (e.g., /java21/J21.0_64, /sdkV24/usr/lpp/IBM/node/v24r0/IBM/node-latest-os390-s390x) will be different on your system depending on which packages are installed and where they are located.
Shell config
# Shell config
export PS1='\u@\h:\w\$ '
shopt -s histappend
export HISTSIZE=5000
export HISTFILESIZE=5000
export EDITOR=vim # or your choice
This section is optional, containing some more variables for customizing Bash. PS1 sets the system prompt, the example given is a standard one but can be customized to your heart's content. The next three are improvements for Bash's history: shopt -s histappend prevents multiple SSH sessions from overwriting the Bash history, while HISTSIZE and HISTFILESIZE increase the number of commands saved in the shell history from its default of 500. EDITOR sets your preferred text editor (used by programs such as git), which in my case is vim, set it to whatever you like.
Verify Environment
In a new terminal, connect to USS via SSH, using your TSO username and password:
ssh <username>@<system IP>
Run the following commands to verify that the installation is correct:
echo $BASH_VERSION # confirms exec-to-bash worked
echo $_BPXK_AUTOCVT # confirms env vars set
type bash # confirms zopen bash
echo $PATH | tr ':' '\n' # confirms zopen paths present
git --version # confirms a zopen tool can run from PATH
You should see something like:
griffin@STLAB57:~$ echo $BASH_VERSION
5.3.9(1)-release
griffin@STLAB57:~$ echo $_BPXK_AUTOCVT
ON
griffin@STLAB57:~$ type bash
bash is /home/griffin/zopen/usr/local/bin/bash
griffin@STLAB57:~$ echo $PATH | tr ':' '\n'
/home/griffin/zopen/usr/local/altbin
/home/griffin/zopen/usr/local/bin
/home/griffin/zopen/usr/bin
/home/griffin/zopen/bin
/home/griffin/zopen/usr/local/sbin
/home/griffin/zopen/boot
/bin
/java21/J21.0_64/bin
/sdkV24/usr/lpp/IBM/node/v24r0/IBM/node-latest-os390-s390x/bin
/pyth314/usr/lpp/IBM/python/v3r14/pyz/bin
/xlc220/usr/lpp/IBM/cnw/v2r2/openxl/bin
griffin@STLAB57:~$ git --version
git version 2.54.0Putting it all together
These are the actual contents of my logon scripts as they appear on one of my machines. Note some minor additions such as set -o vi and a secrets file which contains API keys and such.
# /home/griffin/.profile
# Just enough to locate bash, then become bash.
export _BPXK_AUTOCVT=ON
[ -e "${HOME}/zopen/etc/zopen-config" ] && . "${HOME}/zopen/etc/zopen-config"
if [ -z "$BASH_VERSION" ] && command -v bash >/dev/null 2>&1; then
exec bash -l
fi# /home/griffin/.bash_profile
[ -r ~/.bashrc ] && . ~/.bashrc# /home/griffin/.bashrc
# USS file tagging and conversion support
export _BPXK_AUTOCVT=ON
export _CEE_RUNOPTS="FILETAG(AUTOCVT,AUTOTAG) POSIX(ON)"
export _TAG_REDIR_ERR=txt
export _TAG_REDIR_IN=txt
export _TAG_REDIR_OUT=txt
# source zopen but don't emit
if [ -e "${HOME}/zopen/etc/zopen-config" ]; then
. "${HOME}/zopen/etc/zopen-config" >/dev/null
fi
# Software install locations, to be added to the PATH
export JAVA_HOME=/java21/J21.0_64
export NODE_HOME=/sdkV24/usr/lpp/IBM/node/v24r0/IBM/node-latest-os390-s390x
export PYTHON_HOME=/pyth314/usr/lpp/IBM/python/v3r14/pyz
export CPP_HOME=/xlc220/usr/lpp/IBM/cnw/v2r2/openxl
# $PATH config
PATH="${PATH%:}" # fix trailing ':' from zopen-config
PATH="$PATH:$JAVA_HOME/bin"
PATH="$PATH:$NODE_HOME/bin"
PATH="$PATH:$PYTHON_HOME/bin"
PATH="$PATH:$CPP_HOME/bin"
export PATH
# Shell config
export PS1='\u@\h:\w\$ '
shopt -s histappend
export HISTSIZE=5000
export HISTFILESIZE=5000
export EDITOR=vim
set -o vi
[ -f "$HOME/.secrets" ] && . "$HOME/.secrets"
Comments