A PHP CLI utility for Linux that manages filesystem permissions on a REDCap installation, hardening Apache's access during normal operation and temporarily restoring write access during upgrades and external module installations.
REDCap's built-in upgrade wizard and external module installer require the Apache process to write files inside the application directory. Leaving those permissions open permanently is a security risk. This tool automates the two-state permission model:
| State | Owner | Group | Dirs | Files | Apache access |
|---|---|---|---|---|---|
| Locked (normal) | root |
www-data |
0750 |
0640 |
Read-only |
| Unlocked (upgrade) | www-data |
www-data |
0750 |
0640 |
Read + write |
Write access is granted by changing ownership, not by loosening mode bits, so permissions stay tight in both states.
Certain paths are handled specially regardless of state:
- Always protected —
database.phpis always owned by the locked owner so DB credentials are never writable by Apache. - Always writable —
modules/orca_search_v*directories are always owned by the Apache user so the OrcaSearch external module can write its data files even while the rest of the application is locked. - Always writable —
temp/directory is always owned by the Apache user so the Apache (and REDCap) can write its data files even while the rest of the application is locked.
- Linux host running Apache
- PHP 8.1 or later (CLI)
- Must be run as root (via
sudo)
-
Copy the script to a location on the system path:
sudo cp redcap_upgrade_permissions.php /usr/local/bin/redcap_upgrade_permissions.php sudo chmod 0755 /usr/local/bin/redcap_upgrade_permissions.php
-
Save your site-specific options so you don't have to repeat them on every invocation (this also sets up the config for the cron job):
sudo php /usr/local/bin/redcap_upgrade_permissions.php status \ --path=/var/www/html/redcap --timeout=180 --save-optionsOptions that differ from the defaults (e.g.
--apache-user,--owner,--timeout) should be included here. The saved config file is read automatically on every subsequent run. -
Lock the filesystem to establish the initial safe state:
sudo php /usr/local/bin/redcap_upgrade_permissions.php lock
-
Optionally add a cron job to auto-relock if the unlock timeout expires without a manual
lockcommand (runs every minute, silent unless an action is taken):* * * * * root php /usr/local/bin/redcap_upgrade_permissions.php relock --no-color
sudo php redcap_upgrade_permissions.php <command> [options]
| Command | Description |
|---|---|
lock |
Restrict permissions — Apache read-only. The normal, safe state. |
unlock |
Grant Apache write access for an upgrade. Records unlock time for auto-relock. |
status |
Show current state, remaining unlock time, and a live filesystem sample. |
relock |
Re-lock if the unlock timeout has expired. Designed for cron. |
| Option | Default | Description |
|---|---|---|
--path=<dir> |
/var/www/html/redcap |
REDCap installation directory |
--apache-user=<user> |
www-data |
Apache process user |
--apache-group=<grp> |
www-data |
Apache process group |
--owner=<user> |
root |
File owner when locked |
--timeout=<seconds> |
1200 |
Auto-relock after N seconds when unlocked |
--state-file=<path> |
/var/run/redcap_perms.state |
Unlock state file location |
--log-file=<path> |
/var/log/redcap_perms.log |
Log file location |
--config=<path> |
/etc/redcap_upgrade_permissions.conf |
Config file for saved options |
--save-options |
— | Save --path, --apache-user, --apache-group, --owner, --timeout, and --state-file to the config file, then run the command normally |
--dry-run |
— | Print commands without executing them |
--yes |
— | Skip confirmation prompts (non-interactive/scripted use) |
--force |
— | Override skip conditions: reapply if already in state, reset unlock timer, or relock before timeout expires |
--no-color |
— | Disable ANSI color output (recommended for cron/scripts) |
--help |
— | Show help message |
# 1. Verify the current state before starting
sudo php redcap_upgrade_permissions.php status --path=/var/www/html/redcap
# 2. Open write access (20-minute window by default)
sudo php redcap_upgrade_permissions.php unlock --path=/var/www/html/redcap
# 3. Run the REDCap upgrade or external module installation through the browser
# 4. Re-lock immediately once the upgrade or installation is complete
sudo php redcap_upgrade_permissions.php lock --path=/var/www/html/redcap
# 5. Verify
sudo php redcap_upgrade_permissions.php status --path=/var/www/html/redcap# Preview what 'unlock' would do without making any changes
sudo php redcap_upgrade_permissions.php unlock --path=/var/www/html/redcap --dry-run
# Unlock with a custom 2-hour window, skipping the confirmation prompt
sudo php redcap_upgrade_permissions.php unlock --path=/var/www/html/redcap \
--timeout=7200 --yes
# Use non-standard Apache user/group (e.g. RHEL/CentOS)
sudo php redcap_upgrade_permissions.php unlock --path=/var/www/html/redcap \
--apache-user=apache --apache-group=apache
# Force re-lock immediately (e.g. if you forgot to lock after an upgrade)
sudo php redcap_upgrade_permissions.php relock --path=/var/www/html/redcap --forceUse --save-options with any command to persist the six configurable
options (--path, --apache-user, --apache-group, --owner,
--timeout, --state-file) to a JSON config file. Saved values become
the new defaults and are automatically loaded on every subsequent run.
# Save options for your site (run once after installation)
sudo php redcap_upgrade_permissions.php status --path=/var/www/html/redcap \
--apache-user=apache --apache-group=apache --save-options
# All subsequent commands pick up saved options automatically
sudo php redcap_upgrade_permissions.php unlockTo manage two REDCap instances on the same host, use a separate
--config file for each. The --state-file option (also saved) keeps
the unlock state for each instance independent.
# Configure and save options for a second instance
sudo php redcap_upgrade_permissions.php status \
--config=/etc/redcap2_upgrade_permissions.conf \
--path=/var/www/html/redcap2 \
--state-file=/var/run/redcap2_perms.state \
--save-options
# Operate on the second instance by pointing at its config file
sudo php redcap_upgrade_permissions.php unlock \
--config=/etc/redcap2_upgrade_permissions.conf
# Cron entry for the second instance
* * * * * root php /usr/local/bin/redcap_upgrade_permissions.php relock \
--config=/etc/redcap2_upgrade_permissions.conf --no-color- State file (
/var/run/redcap_perms.state) — JSON file recording whether the installation is locked and when it was unlocked. Used bystatusandrelock. Readable only by root (0600). - Log file (
/var/log/redcap_perms.log) — Append-only log of every lock, unlock, and auto-relock action with timestamp and invoking user.
Copyright 2026 University of Florida. Licensed under the Apache License, Version 2.0.