BentoPDF 2.8.10
Overview
BentoPDF is an open-source PDF toolkit with more than a hundred tools: merge and split, compress, organize, rotate, delete and extract pages, convert to and from images and office formats, add watermarks, page numbers and bookmarks, fill forms, sign, redact, compare, and change permissions and metadata. It is privacy-first by design. Every PDF operation runs inside your own web browser, so your documents are processed on your own machine and are never uploaded to the server. The server only delivers the web application.
Upstream BentoPDF has no login of its own. This SecureAnyCloud image turns it into a private PDF toolkit for your team, on the BentoPDF 2.8.10 on Ubuntu 24.04 AMI for Amazon EC2. It is one version of the SecureAnyCloud self-hosted personal apps listing on AWS Marketplace, which you select as the software version when you subscribe and launch.
What you get:
- HTTPS from the first boot. nginx serves BentoPDF on port 443 with a certificate generated on your instance's first boot. Port 80 redirects to HTTPS.
- A password unique to your instance. HTTP Basic Auth is required for the user
admin. The password is generated on first boot and written to/var/lib/sac/data/bentopdf-credentials.txt(root only), and nginx stores only a bcrypt hash of it. No password is baked into the image. - Fail closed. nginx refuses to start until first boot has generated the certificate and the password, so the toolkit is never served without a login.
- Hardened defaults. Requests for any host name the instance does not serve are dropped, only
GETandHEADare accepted, cross-site requests are refused, nginx is blocked from the instance metadata service, and the AMI requires IMDSv2. BentoPDF's own Content Security Policy is served, which limits where the page may load code from and connect to. - Stock upstream release. The official BentoPDF 2.8.10 self-hosted release bundle, checked against a pinned SHA-256 and unmodified, with its complete source code on the image.
- Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.
What is on the image: BentoPDF 2.8.10 (the static release in /var/www/bentopdf, with the source and license in /usr/share/bentopdf), 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 that you use for management).
- A security group that allows TCP 22 (SSH), TCP 443 (BentoPDF) and TCP 80 (redirects to HTTPS) from your own networks only.
Launch the instance
In the AWS Marketplace listing, choose Continue to Subscribe, then Continue to Configuration. Select BentoPDF 2.8.10 on Ubuntu 24.04 as the software version, select your Region, then choose Launch through EC2. Choose m5.large, your key pair and a security group as above. BentoPDF itself needs very little on the server, because the PDF work happens in your users' browsers.
To launch with the AWS CLI instead, 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=bentopdf}]'Connect to your instance
Version SSH user
BentoPDF 2.8.10 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>Check the services
First boot generates the certificate and the password, then starts nginx. Check the units:
systemctl is-active nginx bentopdf-firstboot
sudo test -f /var/lib/sac/data/bentopdf-firstboot.done && echo FIRSTBOOT_DONEBoth units are active, and the first-boot marker is present:
active active FIRSTBOOT_DONEOnly SSH and nginx listen on the network. Confirm the listeners:
sudo ss -lntp | awk 'NR>1 && $4 !~ /^127\./ {print $4, $6}' | sort0.0.0.0:22 users:(("sshd",...),("systemd",...)) 0.0.0.0:443 users:(("nginx",...)) 0.0.0.0:80 users:(("nginx",...)) [::]:22 users:(("sshd",...),("systemd",...)) [::]:443 users:(("nginx",...)) [::]:80 users:(("nginx",...))Get your URL and password
sudo cat /var/lib/sac/data/bentopdf-credentials.txtOnly root can read the file. It holds the URL, the user name and this instance's password:
BENTOPDF_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/ BENTOPDF_USERNAME=admin BENTOPDF_PASSWORD=<BENTOPDF_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 BentoPDF under your own DNS name, see Step 8.
Sign in from your browser
Open the BENTOPDF_URL in your browser. The certificate is self-signed, so your browser asks you to confirm it the first time. To avoid this, install your own certificate (Step 8).
Sign in as admin with the password from Step 4. The browser remembers the login for the session.
The home page lists the tools as cards, with Popular Tools first and then every category. Use the search box (Cmd+K or Ctrl+K) to jump to a tool.
Work with your PDFs
Merge PDF combines several documents into one. Drop the files on the page, or click to select them. Drag the handle on a file to change the order. Optionally type a page range such as 1-3, 5 for a file, then choose Merge PDFs. The merged file is built in your browser and downloaded straight away.
Compress PDF reduces the size of a document. Pick the algorithm and level, then choose Compress PDF. The recommended defaults suit most files.
When it finishes, BentoPDF reports the method used and the size saved, and downloads the result.
Every tool has its own address. You can bookmark or share a link such as https://<your-host>/merge-pdf or https://<your-host>/compress-pdf with your team, who sign in with the same password.
What leaves the browser. Your documents never do. Most tools use only the code served by your instance. A few heavy tools (some conversions, compression engines and OCR) download their open-source WebAssembly engine from the public jsDelivr CDN the first time a browser uses them. Digital signature timestamping goes through BentoPDF's own proxy. The Content Security Policy served by the instance allows exactly those destinations.
If your users' browsers have no internet access, the tools that need those engines do not run. Everything else works.
Check the security posture from the command line
Without a login, the toolkit is refused. Port 80 redirects to HTTPS, the health check stays open for load balancers, and a request for a host name that the instance does not serve is dropped without an answer.
H=$(sudo grep '^BENTOPDF_URL=' /var/lib/sac/data/bentopdf-credentials.txt | sed -E 's#^BENTOPDF_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 'health -> HTTP %{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/healthz"
curl -sk -o /dev/null -w 'unknown host -> %{http_code}\n' --resolve "evil.example:443:127.0.0.1" "https://evil.example/" || true
curl -sk --resolve "$H:443:127.0.0.1" "https://$H/" | grep -o -m1 '401 Authorization Required'http:// -> 301 https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/ no login -> HTTP 401 health -> HTTP 200 unknown host -> 000 401 Authorization Required000 means that nginx closed the connection without answering, which is how it treats any host name it does not serve.
With the password, BentoPDF is served. The password is passed to curl on standard input, so it never appears in the process list or your shell history.
sudo bash -c ' F=/var/lib/sac/data/bentopdf-credentials.txt P=$(grep "^BENTOPDF_PASSWORD=" $F | cut -d= -f2-) H=$(grep "^BENTOPDF_URL=" $F | sed -E "s#^BENTOPDF_URL=https://([^/]+)/.*#\1#") auth() { printf "user = \"admin:%s\"\n" "$P"; } auth | curl -K - -sk --resolve "$H:443:127.0.0.1" -o /dev/null -w "with login -> HTTP %{http_code}\n" "https://$H/" auth | curl -K - -sk --resolve "$H:443:127.0.0.1" -o /dev/null -w "merge tool -> HTTP %{http_code}\n" "https://$H/merge-pdf" auth | curl -K - -skI --resolve "$H:443:127.0.0.1" "https://$H/" | grep -iE "^cross-origin-(opener|embedder)-policy" 'with login -> HTTP 200 merge tool -> HTTP 200 cross-origin-opener-policy: same-origin cross-origin-embedder-policy: credentiallessnginx 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/.bentopdf-htpasswd
sudo stat -c '%a %U:%G %n' /etc/nginx/.bentopdf-htpasswd /var/lib/sac/data/bentopdf-credentials.txtadmin:$2y$12$<bcrypt-hash> 640 root:www-data /etc/nginx/.bentopdf-htpasswd 600 root:root /var/lib/sac/data/bentopdf-credentials.txtThe certificate was generated on this instance and names its own addresses.
H=$(sudo grep '^BENTOPDF_URL=' /var/lib/sac/data/bentopdf-credentials.txt | sed -E 's#^BENTOPDF_URL=https://([^/]+)/.*#\1#')
echo | openssl s_client -connect 127.0.0.1:443 -servername "$H" 2>/dev/null | openssl x509 -noout -subject -ext subjectAltNamesubject=CN = ec2-<public-ip-dashed>.<region>.compute.amazonaws.com, O = sac 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:localhostnginx cannot reach the instance metadata service, and it does not start until first boot has finished.
systemctl show nginx -p IPAddressDeny | grep 169.254.169.254
systemctl cat nginx | grep -E '^ExecStartPre=/usr/bin/test'IPAddressDeny=168.63.129.16/32 fd00:ec2::254/128 169.254.169.254/32 ExecStartPre=/usr/bin/test -f /var/lib/sac/data/bentopdf-firstboot.done ExecStartPre=/usr/bin/test -s /etc/nginx/.bentopdf-htpasswdnginx also refuses a request whose Origin is another web site (HTTP 403), because browsers resend Basic Auth credentials on cross-site requests.
H=$(sudo grep '^BENTOPDF_URL=' /var/lib/sac/data/bentopdf-credentials.txt | sed -E 's#^BENTOPDF_URL=https://([^/]+)/.*#\1#')
curl -sk --resolve "$H:443:127.0.0.1" -H 'Origin: https://evil.example' -d x=1 "https://$H/" | grep -o -m1 '403 Forbidden'403 ForbiddenYour own DNS name and certificate
Point a DNS record at the instance, then set the name that BentoPDF is served as. This creates a new self-signed certificate for the name, updates nginx and the URL in the credentials file, and keeps the name across reboots.
sudo bentopdf-set-hostname <your-domain>To use a certificate from your own certificate authority instead, copy it over the generated pair and reload nginx. Running bentopdf-set-hostname again replaces it with a self-signed certificate.
sudo install -m 0644 <your-domain>.crt /etc/ssl/bentopdf/bentopdf.crt
sudo install -m 0600 <your-domain>.key /etc/ssl/bentopdf/bentopdf.key
sudo nginx -t && sudo systemctl reload nginxChange 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, then reload nginx. printf is a shell builtin, so the password never appears in the process list. Start the line with a space so that most shells keep it out of your history.
printf '%s\n' '<new-password>' | sudo htpasswd -i -B -C 12 /etc/nginx/.bentopdf-htpasswd admin
sudo systemctl reload nginxRunning sudo htpasswd -B -C 12 /etc/nginx/.bentopdf-htpasswd <user> without -i prompts for the password instead. Use it with another user name to give a colleague their own login, and use sudo htpasswd -D /etc/nginx/.bentopdf-htpasswd <user> to remove one.
Afterward, update or delete BENTOPDF_PASSWORD in /var/lib/sac/data/bentopdf-credentials.txt so that the file does not hold an old password.
Server components
Component Path
BentoPDF static release /var/www/bentopdf
Source and license (AGPL-3.0) /usr/share/bentopdf/
nginx site /etc/nginx/sites-available/sac-bentopdf
Security headers /etc/nginx/bentopdf-headers.conf
Basic Auth file /etc/nginx/.bentopdf-htpasswd (bcrypt)
Certificate and key /etc/ssl/bentopdf/
First boot script /usr/local/sbin/bentopdf-firstboot.sh
Host name helper /usr/local/sbin/bentopdf-set-hostname
Credentials /var/lib/sac/data/bentopdf-credentials.txt (mode 0600)
ls -l /usr/share/bentopdf/ | awk 'NR>1{print $1,$5,$9}' du -sh /var/www/bentopdf | awk '{print $1, $2}' nginx -v 2>&1-rw-r--r-- 34523 LICENSE -rw-r--r-- 755 README.source.txt -rw-r--r-- 151927366 bentopdf-v2.8.10-source.tar.gz 253M /var/www/bentopdf nginx version: nginx/1.24.0 (Ubuntu)Security 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 9).
- Use your own DNS name and a certificate that your clients trust (Step 8).
- Keep the operating system patched. Unattended security upgrades are on, and
sudo apt-get update && sudo apt-get upgradeapplies everything else.
Support and licensing
BentoPDF is open source under the GNU Affero General Public License v3.0. There is no per-user or per-CPU license fee for running it under that license. The image serves the unmodified upstream release, and the complete source of that exact version is in /usr/share/bentopdf/ (also at https://github.com/alam00000/bentopdf/tree/v2.8.10).
If you modify BentoPDF and let others use it over a network, the AGPL requires you to offer them your modified source. BentoPDF's authors also sell a commercial license for deployments that cannot meet that requirement. The AMI price covers SecureAnyCloud's hardening, first-boot setup and support.
- Email: sales@secureanycloud.com
- Website: https://secureanycloud.com
- Support: 24/7, with a 24-hour response SLA
Deploy on AWS
Launch BentoPDF 2.8.10 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com