grpcUI 1.5.4 on AWS User Guide
Overview
This guide covers launching and using the grpcUI 1.5.4 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg developer and API tools listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.
grpcUI is an open source web UI for gRPC services, a browser based Postman for gRPC. It reads a gRPC server's services and message types through server reflection (or from your .proto / protoset files), draws a form for every method, and when you press Invoke it makes the real gRPC call from the server and shows the response, headers, trailers and errors. Unary, server streaming, client streaming and bidirectional methods all work, and every call has a ready grpcurl command line.
grpcUI has no login of its own (its own documentation says to put it behind authentication), so anyone who can reach it can call your services. The cloudimg image makes it a private gRPC console for your team:
- HTTPS from the first boot. nginx serves grpcUI on port 443 with a certificate generated on your instance's first boot. Port 80 redirects to HTTPS.
- A password unique to your instance, on every path. HTTP Basic Auth is required for the user
adminon every page, asset and endpoint (the web form, method schemas, examples and the invoke endpoint). The password is generated on first boot and written to/var/lib/cloudimg/grpcui-credentials.txt(root only); nginx stores only a bcrypt hash of it. No password is baked into the image. - Ready to try. A small demo gRPC server (grpcbin, with server reflection on) runs on the instance, so grpcUI lists callable services as soon as you sign in. Pointing grpcUI at your own service is one systemd drop-in (Step 7).
- The metadata service is out of reach. grpcUI makes gRPC calls from the server. Its target is fixed in its service configuration (no request can change the address it dials), and grpcUI, the demo server and nginx are all blocked from the EC2 instance metadata service at the kernel level, even if grpcUI is pointed at the metadata address.
- Built from source with patched modules. The upstream release binaries were built with older Go and gRPC versions that carry known advisories. This image builds grpcUI 1.5.4 and the demo server from their release tags with Go 1.27.3 and fixed
golang.org/x/net,golang.org/x/cryptoandgoogle.golang.org/grpcmodules;govulncheckreports no vulnerability that the code reaches. No Go toolchain or compiler ships. - Hardened defaults. grpcUI and the demo server listen on 127.0.0.1 only and run as their own unprivileged users with no capabilities and a read only view of the system. nginx refuses to start until first boot has generated the certificate and the password, drops requests for host names the instance does not serve, refuses cross site requests that call methods, and the AMI requires IMDSv2.
- Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.
What is on the image: grpcUI 1.5.4 (/usr/local/bin/grpcui), grpcbin 1.0.8 demo gRPC server (/usr/local/bin/grpcbin), nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.
Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large
Prerequisites
- An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet (or a private route you use for management).
- A security group that allows TCP 22 (SSH), TCP 443 (grpcUI) and TCP 80 (redirects to HTTPS) from your own networks only.
- To call your own gRPC services, a subnet and security groups that let the instance reach them.
Step 1: Launch the instance
In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick grpcUI 1.5.4 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large (grpcUI itself needs little; a smaller instance works for a few users), your key pair, a security group as above, and a subnet that can reach the gRPC services you want to explore.
With the AWS CLI (replace the values in angle brackets):
aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
--instance-type m5.large --key-name <your-key-pair> \
--security-group-ids <your-security-group> --subnet-id <your-subnet> \
--metadata-options HttpTokens=required,HttpEndpoint=enabled \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=grpcui}]'
Step 2: Connect to your instance
| Version | SSH user |
|---|---|
| grpcUI 1.5.4 on Ubuntu 24.04 | ubuntu |
ssh -i <your-key.pem> ubuntu@<public-ip>
Step 3: Check the services
First boot generates the certificate and the password, then nginx starts in front of grpcUI and the demo server:
systemctl is-active grpc-demo-server grpcui nginx grpcui-firstboot
sudo test -f /var/lib/cloudimg/grpcui-firstboot.done && echo FIRSTBOOT_DONE
All four units are active and the first boot marker is present:
active
active
active
active
FIRSTBOOT_DONE
Only SSH and nginx listen on the network. grpcUI (port 8080) and the demo server (9000, plus its TLS listener on 9001) listen on 127.0.0.1 only:
sudo ss -lntp | awk 'NR>1 {print $4, $6}' | grep -v '127.0.0.5' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g' | sort
0.0.0.0:22 users:(("sshd"),("systemd"))
0.0.0.0:443 users:(("nginx"),("nginx"),("nginx"))
0.0.0.0:80 users:(("nginx"),("nginx"),("nginx"))
127.0.0.1:8080 users:(("grpcui"))
127.0.0.1:9000 users:(("grpcbin"))
127.0.0.1:9001 users:(("grpcbin"))
[::]:22 users:(("sshd"),("systemd"))
[::]:443 users:(("nginx"),("nginx"),("nginx"))
[::]:80 users:(("nginx"),("nginx"),("nginx"))
Step 4: Get your URL and password
sudo cat /var/lib/cloudimg/grpcui-credentials.txt
The file is readable by root only and holds the URL, the user name and this instance's password:
GRPCUI_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/
GRPCUI_USERNAME=admin
GRPCUI_PASSWORD=<GRPCUI_PASSWORD>
The URL uses the instance's public DNS name from EC2 instance metadata. The certificate also names the public and private IP addresses, so https://<public-ip>/ works too. To serve grpcUI under your own DNS name, see Step 9.
Step 5: Sign in and call a method
Open the GRPCUI_URL in your browser. The certificate is self signed, so your browser asks you to confirm it the first time (or install your own certificate, Step 9). Sign in as admin with the password from Step 4; the browser remembers the login for the session.
grpcUI opens connected to the bundled demo server (127.0.0.1:9000). Pick a service and a method, fill in the request fields (tick the box next to an optional field to set it) and press Invoke. Here hello.HelloService / SayHello with the greeting cloudimg:

The Response tab shows the response headers, the response message and the trailers:

The Raw Request tab takes the request as JSON instead, and shows the equivalent grpcurl command. addsvc.Add / Sum with a = 2 and b = 40 returns 42 (64 bit integers are strings in JSON):

The demo server also has streaming methods (hello.HelloService / LotsOfReplies, LotsOfGreetings, BidiHello), the grpcbin.GRPCBin methods for headers, errors and empty messages, and grpc.gateway.examples.examplepb.ABitOfEverythingService (from the gRPC gateway examples), whose messages use most protobuf field types. The History tab keeps your recent requests in your browser.
Step 6: Call grpcUI from the command line
grpcUI's own HTTP endpoints are behind the same login. The invoke endpoint takes a JSON body and the CSRF token that grpcUI sets as a cookie (the password is read from the credentials file and passed to curl on standard input, so it never appears in the process list):
sudo bash -c '
F=/var/lib/cloudimg/grpcui-credentials.txt
P=$(grep "^GRPCUI_PASSWORD=" $F | cut -d= -f2-)
H=$(grep "^GRPCUI_URL=" $F | sed -E "s#^GRPCUI_URL=https://([^/]+)/.*#\1#")
auth() { printf "user = \"admin:%s\"\n" "$P"; }
R="--resolve $H:443:127.0.0.1"; J=$(mktemp)
auth | curl -K - -sk $R -c "$J" -o /dev/null "https://$H/"
T=$(awk "/_grpcui_csrf_token/{print \$NF}" "$J")
auth | curl -K - -sk $R -b "$J" -H "Content-Type: application/json" -H "x-grpcui-csrf-token: $T" \
-H "Origin: https://$H" -d "{\"data\":[{\"greeting\":\"cloudimg\"}]}" \
"https://$H/invoke/hello.HelloService.SayHello" | grep -E "\"(reply|error)\""
auth | curl -K - -sk $R "https://$H/metadata?method=hello.HelloService.SayHello" | grep -E "requestType"
rm -f "$J"
'
"error": null,
"reply": "hello cloudimg"
"requestType": "hello.HelloRequest",
Step 7: Point grpcUI at your own gRPC service
grpcUI talks to one gRPC server, set on its command line in grpcui.service. To explore your own service, override the command with a systemd drop-in. Keep -bind 127.0.0.1 -port 8080 (nginx is the only part that faces the network) and replace the target with your service's address (here grpc.<your-domain>:50051, a host name or IP and port). The empty ExecStart= line clears the original command first. Then restart grpcUI, check that it connected (it reads the service list at start up), and stop the demo server, which is no longer needed:
sudo mkdir -p /etc/systemd/system/grpcui.service.d
sudo tee /etc/systemd/system/grpcui.service.d/override.conf >/dev/null <<'EOF2'
[Service]
ExecStart=
ExecStart=/usr/local/bin/grpcui -bind 127.0.0.1 -port 8080 -connect-fail-fast=false -connect-timeout 60 -plaintext grpc.<your-domain>:50051
EOF2
sudo systemctl daemon-reload
sudo systemctl restart grpcui
systemctl is-active grpcui
journalctl -u grpcui -n 5 --no-pager
sudo systemctl disable --now grpc-demo-server
sudo systemctl edit grpcui opens the same drop-in in an editor if you prefer.
Reload the page in your browser; the header shows Connected to your service. Useful options for the ExecStart line (see grpcui -help):
| Your server | Options |
|---|---|
| Plain text (no TLS) | -plaintext |
| TLS with a public certificate | leave out -plaintext |
| TLS with a private CA | -cacert /etc/grpcui/ca.pem (and -servername <name> if the certificate names another host) |
| Mutual TLS | -cacert /etc/grpcui/ca.pem -cert /etc/grpcui/client.pem -key /etc/grpcui/client.key |
| Self signed, for testing only | -insecure (skips certificate checks) |
| No server reflection | -import-path /etc/grpcui/protos -proto service.proto or -protoset /etc/grpcui/service.protoset (reflection is then off unless you also pass -use-reflection) |
| Only some services | -service <package.Service> (repeat it) |
Put certificate, key and .proto files in a directory grpcUI can read but other users cannot, for example sudo install -d -o root -g grpcui -m 0750 /etc/grpcui with the files root:grpcui mode 0640. The grpcUI service runs as the grpcui user with a read only view of the system, so it can read them but never change them.
Request metadata such as authorization tokens is best entered per call in the Request Metadata table of the web form. A token given with -H or -rpc-header on the command line is stored in the unit file and visible in the process list to local users.
To go back to the demo server, remove the drop-in with sudo systemctl revert grpcui, then sudo systemctl enable --now grpc-demo-server and sudo systemctl restart grpcui.
Step 8: Check the security posture from the command line
Without a login grpcUI is refused, including its schema and invoke endpoints; port 80 redirects to HTTPS; the health check stays open for load balancers; and a request for a host name the instance does not serve is dropped without an answer:
H=$(sudo grep '^GRPCUI_URL=' /var/lib/cloudimg/grpcui-credentials.txt | sed -E 's#^GRPCUI_URL=https://([^/]+)/.*#\1#')
curl -s -o /dev/null -w 'http:// -> %{http_code} %{redirect_url}\n' --resolve "$H:80:127.0.0.1" "http://$H/"
curl -sk -o /dev/null -w 'no login -> HTTP %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/"
curl -sk -o /dev/null -w 'metadata, no login -> HTTP %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/metadata?method=*"
curl -sk -o /dev/null -w 'invoke, no login -> HTTP %{http_code}\n' --resolve "$H:443:127.0.0.1" -X POST -H 'Content-Type: application/json' -d '{}' "https://$H/invoke/hello.HelloService.SayHello"
curl -sk -o /dev/null -w 'health -> HTTP %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/health"
curl -sk -o /dev/null -w 'unknown host -> %{http_code}\n' --resolve "evil.example:443:127.0.0.1" "https://evil.example/" || true
http:// -> 301 https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/
no login -> HTTP 401
metadata, no login -> HTTP 401
invoke, no login -> HTTP 401
health -> HTTP 200
unknown host -> 000
000 means nginx closed the connection without answering, which is how it treats any host name it does not serve.
With the password, grpcUI is served and calls work from its own pages. A call whose browser Origin is another web site is refused (HTTP 403), so another page cannot use your signed in browser to call your services, and the invoke endpoint never makes a call on a plain GET (HTTP 405):
sudo bash -c '
F=/var/lib/cloudimg/grpcui-credentials.txt
P=$(grep "^GRPCUI_PASSWORD=" $F | cut -d= -f2-)
H=$(grep "^GRPCUI_URL=" $F | sed -E "s#^GRPCUI_URL=https://([^/]+)/.*#\1#")
auth() { printf "user = \"admin:%s\"\n" "$P"; }
R="--resolve $H:443:127.0.0.1"; J=$(mktemp)
auth | curl -K - -sk $R -c "$J" -o /dev/null -w "with login -> HTTP %{http_code}\n" "https://$H/"
T=$(awk "/_grpcui_csrf_token/{print \$NF}" "$J")
inv() { auth | curl -K - -sk $R -b "$J" -o /dev/null -w "$1 -> HTTP %{http_code}\n" -H "Content-Type: application/json" \
-H "x-grpcui-csrf-token: $T" -H "Origin: $2" -d "{\"data\":[{\"greeting\":\"x\"}]}" "https://$H/invoke/hello.HelloService.SayHello"; }
inv "invoke, same origin" "https://$H"
inv "invoke, other origin" "https://evil.example"
auth | curl -K - -sk $R -b "$J" -o /dev/null -w "invoke as GET -> HTTP %{http_code}\n" -H "x-grpcui-csrf-token: $T" "https://$H/invoke/hello.HelloService.SayHello"
rm -f "$J"
'
with login -> HTTP 200
invoke, same origin -> HTTP 200
invoke, other origin -> HTTP 403
invoke as GET -> HTTP 405
nginx keeps only a bcrypt hash of the password; the plain text is in the root only credentials file:
sudo sed -E 's/\$2y\$12\$.*/$2y$12$<bcrypt-hash>/' /etc/nginx/.grpcui-htpasswd
sudo stat -c '%a %U:%G %n' /etc/nginx/.grpcui-htpasswd /var/lib/cloudimg/grpcui-credentials.txt
admin:$2y$12$<bcrypt-hash>
640 root:www-data /etc/nginx/.grpcui-htpasswd
600 root:root /var/lib/cloudimg/grpcui-credentials.txt
The certificate was generated on this instance and names its own addresses:
H=$(sudo grep '^GRPCUI_URL=' /var/lib/cloudimg/grpcui-credentials.txt | sed -E 's#^GRPCUI_URL=https://([^/]+)/.*#\1#')
echo | openssl s_client -connect 127.0.0.1:443 -servername "$H" 2>/dev/null | openssl x509 -noout -subject -ext subjectAltName
subject=CN = ec2-<public-ip-dashed>.<region>.compute.amazonaws.com, O = cloudimg
X509v3 Subject Alternative Name:
DNS:ec2-<public-ip-dashed>.<region>.compute.amazonaws.com, IP Address:<public-ip>, IP Address:<private-ip>, IP Address:127.0.0.1, DNS:localhost
grpcUI, the demo server and nginx cannot reach the instance metadata service; grpcUI and the demo server run as their own users with no capabilities and a read only system; and grpcUI's target is set in its unit:
systemctl show grpcui grpc-demo-server nginx -p IPAddressDeny
systemctl show grpcui grpc-demo-server -p User -p ProtectSystem -p CapabilityBoundingSet -p NoNewPrivileges
systemctl cat grpcui | grep -E '^ExecStart='
IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128
IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128
IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128
CapabilityBoundingSet=
User=grpcui
ProtectSystem=strict
NoNewPrivileges=yes
CapabilityBoundingSet=
User=grpcbin
ProtectSystem=strict
NoNewPrivileges=yes
ExecStart=/usr/local/bin/grpcui -bind 127.0.0.1 -port 8080 -connect-fail-fast=false -connect-timeout 60 -plaintext 127.0.0.1:9000
The demo server always opens a second, TLS listener, which needs a key. Its key pair is generated every time the demo server starts, in memory backed storage (/run/grpcbin, tmpfs) that only the grpcbin user can read; it never reaches the disk and no two instances share it:
sudo ls -l /run/grpcbin/
findmnt -no FSTYPE -T /run/grpcbin
total 8
-rw------- 1 grpcbin grpcbin 615 <date> loopback.crt
-rw------- 1 grpcbin grpcbin 241 <date> loopback.key
tmpfs
Step 9: Your own DNS name and certificate
Point a DNS record at the instance, then set the name grpcUI is served as. This mints a new self signed certificate for the name, updates nginx, the cross site protection and the URL in the credentials file, and keeps the name across reboots:
sudo grpcui-set-hostname <your-domain>
To use a certificate from your own certificate authority instead, copy it over the generated pair and reload nginx (running grpcui-set-hostname again replaces it with a self signed one):
sudo install -m 0644 <your-domain>.crt /etc/ssl/grpcui/grpcui.crt
sudo install -m 0600 <your-domain>.key /etc/ssl/grpcui/grpcui.key
sudo nginx -t && sudo systemctl reload nginx
Step 10: Change the password or add users
The login is an nginx Basic Auth file. To set a new password for admin, hand it to htpasswd on standard input (printf is a shell builtin, so the password never appears in the process list; start the line with a space so most shells keep it out of your history), then reload nginx:
printf '%s\n' '<new-password>' | sudo htpasswd -i -B -C 12 /etc/nginx/.grpcui-htpasswd admin
sudo systemctl reload nginx
Running sudo htpasswd -B -C 12 /etc/nginx/.grpcui-htpasswd <user> without -i prompts for the password instead. Use it with another user name to give a colleague their own login, and sudo htpasswd -D /etc/nginx/.grpcui-htpasswd <user> to remove one. Then update or delete GRPCUI_PASSWORD in /var/lib/cloudimg/grpcui-credentials.txt so it does not hold an old password.
nginx checks the login and does not pass it on to grpcUI or to your gRPC service.
Step 11: Server components
| Component | Path |
|---|---|
| grpcUI binary | /usr/local/bin/grpcui |
| Demo gRPC server (grpcbin) | /usr/local/bin/grpcbin |
| Build record, licences, module list, govulncheck reports | /usr/share/doc/cloudimg-grpcui/ |
| grpcUI service | grpcui.service (user grpcui, 127.0.0.1:8080) |
| Demo server service | grpc-demo-server.service (user grpcbin, 127.0.0.1:9000 and 9001) |
| nginx site | /etc/nginx/sites-available/cloudimg-grpcui |
| Basic Auth file | /etc/nginx/.grpcui-htpasswd (bcrypt) |
| Certificate and key | /etc/ssl/grpcui/ |
| First boot script | /usr/local/sbin/grpcui-firstboot.sh |
| Host name helper | /usr/local/sbin/grpcui-set-hostname |
| Credentials | /var/lib/cloudimg/grpcui-credentials.txt (mode 0600) |
grpcui -version
ls /usr/local/bin
command -v go || echo "no Go toolchain"
nginx -v 2>&1
grep -h '^Your code' /usr/share/doc/cloudimg-grpcui/govulncheck-*-binary.txt
grpcui v1.5.4
grpcbin
grpcui
no Go toolchain
nginx version: nginx/1.24.0 (Ubuntu)
Your code is affected by 0 vulnerabilities.
Your code is affected by 0 vulnerabilities.
/usr/share/doc/cloudimg-grpcui/BUILD-INFO.txt records the exact source archives and commits, the Go toolchain, the module versions raised to fixed releases and the binary checksums; the govulncheck-*.txt files hold the full binary and source mode reports.
Step 12: Security notes and recommendations
- Allow ports 22, 80 and 443 in the security group from your own networks only.
- Keep the per instance password secret, and rotate it if it may have been shared (Step 10).
- Use your own DNS name and a certificate your clients trust (Step 9).
- grpcUI calls whatever service you point it at with the requests your users send. Give the instance a security group and subnet that reach only the gRPC services it should explore, and use a test or staging service where you can.
- Known items with no fixed upstream release, reviewed for this image:
govulnchecklists GO-2026-5932 (the deprecatedgolang.org/x/crypto/openpgppackage, which has no fixed version) as present in a required module, but neither program imports or contains that package. grpcUI's web page bundles jQuery 3.4.1 and jQuery UI 1.12.1, which have published cross site scripting advisories (CVE-2020-11022, CVE-2020-11023, CVE-2021-41182, CVE-2021-41183, CVE-2021-41184) for pages that pass untrusted HTML or options to them; grpcUI inserts service names, field names and response values as text, its date picker options are fixed, and the page is only served behind your login. cloudimg rechecks both at each grpcUI release. - Keep the operating system patched; unattended security upgrades are on, and
sudo apt-get update && sudo apt-get upgradeapplies everything else.
Support and licensing
grpcUI is open source under the MIT licence (Fullstory), and the bundled demo server grpcbin is open source under the MIT licence (Manfred Touron); both licence texts are in /usr/share/doc/cloudimg-grpcui/. There is no per user or per CPU licence fee. The AMI price covers cloudimg's build, hardening, first boot setup and support.
- Email: support@cloudimg.co.uk
- Website: https://www.cloudimg.co.uk
- Support: 24/7, with a 24 hour response SLA