To assist in troubleshooting, I wanted to generate JWT (Javascript Web Tokens) on-the-fly with bash.

It was the easiest way (I thought) to be able to test various conditions like malformed Javascript headers, payloads, mismatching algorithms, and various other edge cases to see how my server would respond.

This nginx blog post and this superuser post were very helpful in getting my script working.

For most people, you might find that the interactive debugger available at is actually a much better way to generate JWTs. You can click that link and live edit either the generated token on the left, or the content on the right. It’s very nice and simple.

However, since I already spent all this time and energy to do it in bash, I wanted to share my results.

Edit the SECRET, HEADER, and PAYLOAD in this script as needed.

You must have jq and openssl installed for this to work.

This will only generate JWTs with HMAC signing using SHA256. I’ve seen this signing referred to as both HS256 and HMACSHA256.

#!/usr/bin/env bash

set -euo pipefail

    "typ": "JWT",
    "alg": "HS256",
    "kid": "0001",
    "iss": "Bash JWT Generator",
    "exp": '$(($(date +%s)+1))',
    "iat": '$(date +%s)'
    "Id": 1,
    "Name": "hey there"

function base64_encode()
    declare INPUT=${1:-$(</dev/stdin)};
    echo -n "$INPUT" | openssl enc -base64 | tr '+\/' '-_' | tr -d '=' | tr -d '\r\n'

# For some reason, probably bash-related, JSON that terminates with an integer
# must be compacted. So it must be something like `{"userId":1}` or else the
# signing gets screwed up. Weird, but using `jq -c` works to fix that.
function json() {
    declare INPUT=${1:-$(</dev/stdin)};
    echo -n "$INPUT" | jq -c .

function hmacsha256_sign()
    declare INPUT=${1:-$(</dev/stdin)};
    echo -n "$INPUT" | openssl dgst -binary -sha256 -hmac "${SECRET}"

HEADER_BASE64=$(echo "${HEADER}" | json | base64_encode)
PAYLOAD_BASE64=$(echo "${PAYLOAD}" | json | base64_encode)

SIGNATURE=$(echo "${HEADER_PAYLOAD}" | hmacsha256_sign | base64_encode)


Running the script should generate an encoded JWT that looks like this.


You can use the JWT Debugger to verify the string is valid and properly signed.