Secure Dice is a free, account-free online dice roller for tabletop roleplaying games. It uses PHP's cryptographically secure random-number generator and presents each roll as readable arithmetic.
- Flexible dice pools: Roll 1 to 20 dice with common die sizes from d2 through d100 and modifiers from -60 to +60.
- Combined rolls: Add or subtract a second dice pool, with its own die size, modifier, and roll mode.
- Special modes: Sum every die, drop the lowest or highest die, use a D6 System wild die, use a Dragon Age stunt die, or roll Fudge dice.
- Repeated sets: Generate as many as 100 sets at once, optionally sorted by final total.
- Predictable URLs: Copy or bookmark a URL that restores the complete roll setup.
- Readable results: Results show individual dice, dropped and special dice, modifiers, arithmetic, final totals, and summary statistics.
- Authenticated records: Every completed roll is stored under a random 128-bit result ID and can be verified against the server's immutable copy.
- Portable records: Copy or download the exact canonical result data as JSON.
- Recipient consent: Email recipients confirm once and can pause, manage, or revoke delivery without an account.
- Queued email delivery: Stored results are sent through authenticated SMTP with batching, retries, delivery history, and per-message unsubscribe links.
Secure Dice is available at RPG Library.
Permanent ZIP packages are available from GitHub Releases. Each release provides SecureDice-<version>.zip and is published only when a matching version tag, such as v2.0.50, is pushed.
Every commit to main also creates a development build under GitHub Actions. Development archives are named SecureDice-<version>-dev-<commit>.zip, require a GitHub sign-in to download, and are retained for 90 days.
- Choose the number and type of dice for the Primary Roll.
- Optionally add a bonus or penalty and select a roll mode.
- Optionally configure a Secondary Roll to add to or subtract from the primary result.
- Choose how many sets to roll and whether to sort them by final total.
- Select Roll Dice.
The Copy URL button copies the current setup as a reusable preset. Reset restores the default 3d6 roll.
The primary pool supports 1 to 20 dice. Available standard dice are d2, d3, d4, d5, d6, d7, d8, d9, d10, d12, d13, d18, d20, d25, d30, d35, d40, d45, d50, d60, d70, d80, d90, and d100. It also supports d6-based Fudge dice.
Each standard pool can use a modifier from -60 to +60. The available roll modes are:
- Sum them all: Every die contributes to the pool total.
- Drop the lowest die: Rolls at least two dice and removes one lowest result.
- Drop the highest die: Rolls at least two dice and removes one highest result.
- Wild die: Uses at least two d6s. The final die is wild; a 6 explodes, while a 1 removes itself and the highest other die.
- Stunt die: Rolls exactly 3d6 and identifies the final die as the stunt die.
- Fudge dice: Converts each d6 result to minus, blank, or plus and sums the converted values.
Wild dice, stunt dice, and Fudge dice use only the primary pool. Selecting one of these modes disables the secondary pool.
The secondary pool supports up to 20 standard dice and can be added to or subtracted from the primary pool. It supports summing, dropping the lowest die, and dropping the highest die.
Repeat options include 1 through 20 sets and then 30 through 100 sets in increments of 10. Sorted results are ordered from highest final total to lowest while retaining their original set numbers.
Each result set displays its dice and modifier as an arithmetic expression followed by the final total. Dropped dice remain visible but are marked as excluded. Wild-die explosions, wild complications, stunt dice, bonuses, and penalties receive distinct treatments.
The results page also provides:
- Minimum, maximum, and average final totals.
- A preset URL for rolling the same specification again.
- Canonical JSON containing the specification, generated time, individual dice, and totals.
- A permanent verification link and random result ID.
- Buttons for opening the verified record and copying its link or canonical JSON.
- Recipients chosen before the roll, with queued email to as many as 10 independently opted-in addresses.
Each roll is stored as an immutable canonical record before its results page is displayed. To verify a roll, open its verification link or enter its 32-character result ID on verify.php. Secure Dice retrieves its authoritative database copy, verifies its server-secret HMAC and SHA-256 corruption check, validates the record structure, and then renders the stored result.
Successful verification establishes that the result was authenticated by that Secure Dice installation and remains unchanged. The HMAC is not a public-key signature or proof independent of the server, its secret, and its HTTPS identity. See SECURITY.md for the threat model and residual risks.
Verification links do not depend on the browser session that generated the roll. Verified canonical JSON can also be downloaded from the verification page.
An address must confirm its opt-in once before Secure Dice will send results to it:
- The recipient follows Opt in to result email or manage consent and submits an address on
recipient.php. - Secure Dice stores the address encrypted and emails a single-use confirmation link that expires after 24 hours. Opening it presents a confirmation button; only that CSRF-protected action activates delivery.
- Confirmation activates the address and provides a private settings link. No code needs to be shared with a roller.
- A roller enters up to 10 addresses before rolling. Secure Dice silently queues the resulting roll only for active, available recipients. Results cannot be selected for email after they are seen.
- Result email includes readable roll arithmetic, the authoritative verification link, aggregate recipient counts, and a per-message unsubscribe link.
- The private settings link can select Tabletop session, Occasional, or Paused delivery, or revoke consent immediately. Submitting an active address on the opt-in form emails a one-time recovery link for replacing the private settings link.
No custom subject, sender identity, or message text is accepted. Non-opted-in addresses receive nothing. Delivered messages report only aggregate counts—for example, that eight of ten intended recipients were opted in—without naming or listing another recipient.
Enrollment and result-request responses are deliberately generic. Raw addresses and IP addresses are not retained for lookup or rate limiting: addresses are encrypted with keyed fingerprints, and rate buckets use keyed hashes. Consent and email forms use CSRF protection, private no-store responses, strict same-site cookies, and a restrictive browser security policy.
Tabletop delivery permits 20 results per 10 minutes, 150 per hour, and 1,000 per day per recipient. Occasional delivery permits 10 per 10 minutes, 20 per hour, and 100 per day. Sender IPs are limited to 300 submissions per hour, mixed opted-in/non-opted-in groups receive separate throttling, each result-recipient pair can be delivered only once, and SMTP output is capped at 60 messages per minute and 500 per hour. Replays do not consume recipient delivery limits. Nearby results for the same recipient are held briefly and combined, up to 20 results per message.
The command-line queue worker atomically claims messages, rechecks consent before sending, retries temporary failures with increasing delays, stops after five attempts, records sanitized delivery history, and purges encrypted message payloads after success or permanent failure.
Secure Dice encodes roll settings in ordinary query parameters so presets can be bookmarked or shared.
| Parameter | Purpose | Example |
|---|---|---|
aq |
Primary dice count, 1 to 20 | aq=4 |
as |
Primary die sides | as=12 |
am |
Primary modifier, -60 to +60 | am=3 |
ad |
Primary mode: none, lowest, highest, wild, or stunt |
ad=wild |
af |
Select Fudge dice when set to 1 |
af=1 |
bq |
Secondary dice count, -20 to 20; the sign determines addition or subtraction | bq=-2 |
bs |
Secondary die sides | bs=8 |
bm |
Secondary modifier, -60 to +60 | bm=-1 |
bd |
Secondary mode: none, lowest, or highest |
bd=highest |
dt |
Number of repeated sets | dt=10 |
sdt |
Sort sets by total when set to 1 |
sdt=1 |
Example:
https://www.rpglibrary.org/software/securedice/securedice.php?aq=7&as=4&dt=5&ad=highest&sdt=1
For a production installation, including DreamHost setup, first-run testing, cron, backups, upgrades, rollback, and lost-secret recovery, follow DEPLOYMENT.md.
Place the repository files in a PHP-enabled web directory and direct users to securedice.php. The included .htaccess makes securedice.php the default page on Apache and redirects explicit requests for index.php.
Secure Dice requires:
- PHP with
random_int(), session support, Sodium, PDO, and the PDO MySQL driver. - Composer for source installations. Release ZIPs already include production dependencies.
- A web server capable of running PHP.
- Browser cookies for the short-lived session that transfers a roll to its results page.
- Access to the
rpglibrary_orgMySQL database ondb.rpglibrary.org.
Secure Dice creates its sd2_ MySQL tables in the configured database. Legacy rolls are kept separately in sd2_legacy_rolls; they cannot acquire Secure Dice 2 authentication retroactively.
The repository includes .securedice.env.example, a documented template containing every supported setting. Copy it to .securedice.env in the hosting user's home directory, edit the private copy, and restrict it to the account owner:
cp .securedice.env.example "$HOME/.securedice.env"
chmod 600 "$HOME/.securedice.env"The completed file must remain outside the public website and must never be committed. Secure Dice automatically reads $HOME/.securedice.env for both web requests and the command-line queue worker. Existing process environment variables take precedence over values in the file. A different location can be selected with SECUREDICE_CONFIG_PATH, but that variable must be available to both PHP execution environments.
The parser accepts only documented SECUREDICE_ keys, rejects duplicates and malformed values, and refuses a Unix configuration file readable or writable by group or other users. Blank lines and lines beginning with # are ignored. Values may be unquoted, single quoted, or double quoted as documented in the example.
Set the existing private MySQL connection in the configuration:
SECUREDICE_DB_HOST=db.rpglibrary.org
SECUREDICE_DB_PORT=3306
SECUREDICE_DB_NAME=rpglibrary_org
SECUREDICE_DB_USER=<MySQL user>
SECUREDICE_DB_PASSWORD=<MySQL password>
The configured MySQL account must be able to create the sd2_ tables. No application user accounts are required.
Result authentication and recipient consent require a persistent 256-bit application secret, supplied as exactly 64 hexadecimal characters. Generate it once and store it in the private configuration:
php -r 'echo bin2hex(random_bytes(32)), PHP_EOL;'SECUREDICE_SECRET=<64 hexadecimal characters>
Never commit this value. Back it up as carefully as the database and do not rotate it casually: it authenticates stored results and keys address encryption, private fingerprints, and rate-limit buckets. Replacing it makes existing results fail authentication and existing encrypted recipient records unreadable.
Install PHPMailer when deploying directly from a source checkout:
composer install --no-dev --optimize-autoloaderConfigure the public application URL and authenticated SMTP transport in the private file:
SECUREDICE_BASE_URL=https://www.example.com/securedice
SECUREDICE_SMTP_HOST=smtp.example.com
SECUREDICE_SMTP_PORT=587
SECUREDICE_SMTP_ENCRYPTION=starttls
SECUREDICE_SMTP_USERNAME=securedice@example.com
SECUREDICE_SMTP_PASSWORD=<secret>
SECUREDICE_SMTP_FROM_ADDRESS=securedice@example.com
SECUREDICE_SMTP_FROM_NAME=Secure Dice
SECUREDICE_SMTP_TIMEOUT=15
SECUREDICE_SMTP_ENCRYPTION accepts starttls, smtps, or none. Unencrypted SMTP is accepted only for a relay on localhost. Configure SPF, DKIM, and DMARC for the sender domain.
Run the queue worker every minute with cron. It is safe to run multiple workers because queue claims use leases:
* * * * * cd /absolute/path/to/securedice && /usr/bin/php bin/process-email-queue.php --limit=100The worker logs operational details to the PHP error log without returning SMTP errors, addresses, passwords, or private tokens to site visitors.
The example configuration uses the existing fully hosted webmaster@rpglibrary.org mailbox for SMTP authentication and the forward-only securedice@rpglibrary.org as the visible From address. Its replies continue to forward to the configured destination. DreamHost SMTP uses smtp.dreamhost.com, port 587, and starttls; test both sending and a reply.
Upload a release ZIP or source checkout beneath the domain, but keep .securedice.env directly under /home/YOUR_DREAMHOST_USER/ rather than under rpglibrary.org/. The template's opening comment contains the corresponding copy, permission, directory, and secret-generation commands.
Create the queue worker in DreamHost's Cron Jobs panel, select the website's Shell user, enable locking, and run it every minute. Replace the username and installation path in this command:
/usr/local/php84/bin/php /home/YOUR_DREAMHOST_USER/rpglibrary.org/software/securedice/bin/process-email-queue.php --limit=100 --quiet
Omit --quiet while initially testing so the worker prints a short status line. In scheduled operation, --quiet suppresses routine success output while errors still reach standard error and the PHP error log.
Stored result records contain the canonical version-2 JSON, generation time, schema version, random public ID, a SHA-256 corruption check, and a server-secret HMAC. The application inserts results without a write-back path; an HMAC detects direct database changes when they are read.
The integration tests require a dedicated disposable MySQL database whose name begins
with securedice_test_; they refuse to run against another database. Set
SECUREDICE_TEST_DB_NAME, SECUREDICE_TEST_DB_HOST, SECUREDICE_TEST_DB_USER,
and SECUREDICE_TEST_DB_PASSWORD before running the storage, verification,
consent, and email tests. The GitHub Actions workflow creates its own database.
php tests/storage-test.php
php tests/verification-test.php
php tests/consent-test.php
php tests/email-test.php
php tests/storage-migration-test.php
php tests/config-test.php
php tests/ui-test.php
php tests/security-test.phpSecure Dice uses 2.0.(build number) versions. The tracked pre-commit hook sets the build number to the number of the commit being created and records the complete version in VERSION. The page footers read both the version and its last-updated date from that file.
Configure a new clone to use the hook with:
git config core.hooksPath .githooksSee CHANGELOG.md for release history.
AI-assisted tools were used during the development of this project. The author reviewed and approved the resulting code and documentation and remains responsible for the project.
Copyright © 2005-2026 Brandon Blackmoor (bblackmoor@blackgate.net)
Licensed under the GNU General Public License v3.0 (GPL-3.0):
https://www.gnu.org/licenses/gpl-3.0.en.html
Source: https://github.com/bblackmoor/securedice