In recurring cPanel server incidents, the Roundcube webmail running on port :2096 or /webmail may suddenly fail and display a generic message:
Oops... something went wrong!
An internal error has occurred. Your request cannot be processed at this time.
Investigating the internal log files of cPanel and Roundcube, we find the following technical root cause:
DB Error: [14] unable to open database file
(SQL Query: INSERT INTO "session" ...)
in /usr/local/cpanel/base/3rdparty/roundcube/program/lib/Roundcube/rcube_db.php
The key point is clear: Roundcube initializes successfully but crashes when attempting to persist the user's session. This indicates a write permission error or corruption in the SQLite database layer, rather than an internal bug in the Webmail PHP application.
1. What error [14] indicates in the SQLite context#
In the SQLite engine, error code [14] maps directly to SQLITE_CANTOPEN (unable to open the database file). Within the cPanel/Roundcube ecosystem, when the failure occurs during write operations (such as INSERT INTO session), the causes are usually divided into:
- Inaccessible file or directory: Incorrect permissions preventing the Roundcube process (usually executed under the cPanel account user) from reading or writing to the database file.
- Inability to generate Lock/Journal files: SQLite needs permission to create temporary files (such as
.db-journal,.db-wal, or.db-shm) in the same folder where the.dbfile resides. If the parent directory lacks write permissions, SQLite will fail even if the.dbfile has 777 permissions. - Resource Exhaustion: Out of disk space or exhaustion of free inodes on the filesystem.
- Security Blockade: Incorrect file contexts in SELinux or virtualization jail restrictions (CageFS/CloudLinux).
2. Identifying active versions#
Before applying any fixes, validate the control panel and application versions for configuration path references:
# Check the installed cPanel version
cat /usr/local/cpanel/version
# Check the Roundcube version integrated into cPanel
cat /usr/local/cpanel/base/3rdparty/roundcube/program/include/rcube.php | grep -i version
3. First layer: account context and SQLite integrity#
Each cPanel email account has its own SQLite database to record contacts, interface preferences, and active sessions. The default path follows this format:
/home/USER/etc/domain.com/[email protected]
3.1 mandatory preventive backup routine#
Before making any manual database changes or deletions, perform a complete backup of the affected account's email settings:
# Create a secure timestamped backup directory
BACKUP_DIR="/root/roundcube-backup-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$BACKUP_DIR"
# Recursively back up the account's etc directory
cp -r /home/USER/etc/ "$BACKUP_DIR/"
# Make a dedicated copy of the SQLite database
cp /home/USER/etc/domain.com/[email protected] "$BACKUP_DIR/"
echo "Backup successfully saved to: $BACKUP_DIR"
3.2 verify SQLite integrity#
If the database file is physically corrupted, SQLite will refuse write connections:
# Run internal database integrity check
sqlite3 /home/USER/etc/domain.com/[email protected] "PRAGMA integrity_check;"
# Expected output: "ok"
# If it returns any other value, the data file is physically corrupted.
# Check file size (0-byte files indicate truncation due to disk space issues)
ls -lh /home/USER/etc/domain.com/[email protected]
# Check for orphan lock or journal files retaining the database
ls -la /home/USER/etc/domain.com/[email protected]*
3.3 correct permissions and ownership#
The process executing the webmail must be the owner of the database file. Apply the secure permission baseline:
# Adjust owner and group of the account's etc directory
chown -R USER:mail /home/USER/etc
# Set restrictive permissions on folders
chmod 750 /home/USER/etc
chmod 750 /home/USER/etc/domain.com
# Set read/write permissions for the SQLite databases
chmod 640 /home/USER/etc/domain.com/*.db
3.4 recreate corrupted database#
If the PRAGMA check returned errors or if the database file is 0 bytes, force a rebuild by moving the old database:
# Rename the database to generate a new clean one on the next login
mv /home/USER/etc/domain.com/[email protected] \
/home/USER/etc/domain.com/[email protected]
Note: Doing this resets visual preferences and local Roundcube contacts. Email messages themselves remain safe in the Maildir.
4. Second layer: PHP SQLite extension#
Roundcube requires SQLite extensions enabled in the PHP version used by the cPanel panel (via EasyApache).
Check if extensions are loaded:#
php -m | grep -i sqlite
# Should return:
# sqlite3
# pdo_sqlite
If not returned, edit the corresponding system php.ini file (/opt/cpanel/ea-phpXX/root/etc/php.ini) and uncomment or add the extensions:
extension=sqlite3
extension=pdo_sqlite
After making changes, restart the associated PHP-FPM service:
/scripts/restartsrv ea-php-fpm
5. Third layer: scanning affected users scope#
To diagnose whether the failure is isolated to a single user or affects the entire server, run a global scan for empty databases or incorrect permissions:
# Locate all Roundcube databases on the server
find /home -name "*.rcube.db" -type f 2>/dev/null
# Locate databases with zero size (sign of previous quota exhaustion)
find /home -name "*.rcube.db" -type f -size 0 -exec ls -la {} \;
6. Fourth layer: CloudLinux & CageFS virtualization#
If the server uses CloudLinux with CageFS, the users' filesystems are isolated in virtualized environments. A lock or issue in mount propagation prevents the user from writing to the database.
Verify if CloudLinux and CageFS are active:#
# Check operating system release
cat /etc/redhat-release
# Check CageFS version
cagefsctl --version
# Check if the affected user is jailed
cagefsctl --list-users | grep USER
Rebuild and force CageFS remount:#
If the error is caused by virtual directory issues, force an update of the virtual mounts:
# Update permissions and mounts for a specific user
cagefsctl --remount USER
# Force global update of CageFS temporary directories and paths
cagefsctl --force-update
7. Fifth layer: inodes, disk space, and quotas in /tmp and /home#
SQLite must create temporary journal files in the system /tmp directory and write permanently to /home. Lack of free inodes or disk space on these partitions will cause error [14].
Check global space and inodes:#
# Check /home and /tmp partitions
df -h /home /tmp
df -i /home /tmp
Check user inode and space quotas:#
# Check if the user has reached their cPanel quota limit
repquota -a | grep USER
# Measure the hosting directory size
du -sh /home/USER/
Validate /tmp permissions and sticky bit:#
The /tmp directory must have 1777 permissions (Sticky Bit) so that multiple system users can read and write their temporary files without conflicts.
ls -ld /tmp
# Should return: drwxrwxrwt
# If permissions are incorrect, fix them:
chmod 1777 /tmp
8. Sixth layer: filesystem integrity#
Storage failures or disk errors can cause the kernel to remount partitions as read-only (ro) to protect data integrity, blocking all write operations.
# Check the status of active mount points
mount | grep -E ' /home | /tmp | / '
If you find the ro flag on key partitions, perform a filesystem check (fsck) after unmounting them safely.
9. Seventh layer: SELinux and security contexts#
In RHEL-based distributions (AlmaLinux, Rocky Linux) running with SELinux set to Enforcing, security contexts can block Apache/cPanel from writing to the /home/USER/etc directory.
Safe SELinux troubleshooting approach:#
- Check current status:
getenforce
- Temporarily set to permissive mode for quick diagnostics (max 5 minutes):
setenforce 0
- Perform the login test in Roundcube.
- Re-enable enforcing mode immediately after the test:
setenforce 1
- If the login succeeded only with SELinux disabled, restore the correct directory contexts:
restorecon -Rv /home/USER/etc
10. Eighth layer: cPanel package and binary repair#
If all physical infrastructure layers are operational, resolve potential binary corruptions or integrated cPanel scripts.
# 1. cPanel command to validate and fix broken RPMs
/scripts/check_cpanel_rpms --fix
# 2. If necessary, reinstall the Roundcube RPM package
dnf reinstall cpanel-roundcube -y
11. Post-fix verification#
After applying the corrections, verify that the database is functioning normally:
# 1. Attempt webmail login (port 2096)
# 2. Check if a new .db file was created under the etc directory
ls -la /home/USER/etc/domain.com/[email protected]
# 3. Audit user-specific Roundcube logs for remaining exceptions
tail -n 20 /home/USER/etc/domain.com/[email protected]/../roundcube/errors.log
# 4. Check permissions and size of the newly generated database file
stat /home/USER/etc/domain.com/[email protected]
Correlation table: symptoms vs. root causes of error [14]#
| Root Cause | Additional Symptom | Quick Diagnosis | Recommended Fix |
|---|---|---|---|
| File Permissions | Database belongs to root | ls -la /home/USER/etc/ | chown -R USER:mail |
| Quota Exhausted | Database file size is 0 bytes | repquota -a or df -h | Increase user quota or clear space |
| Out of Inodes | Free GBs available, but 0 free inodes | df -i | Clear orphan temp/session files |
| CageFS Lock | Login works outside webmail services | cagefsctl --list-users | cagefsctl --remount USER |
| SELinux Context | Write blocks found in audit.log | getenforce | restorecon -Rv /home/USER/etc |
| SQLite Corruption | Reading database throws errors | sqlite3 [file] "PRAGMA..." | Move database to .corrupted |
Support team triage runbook#
Follow these sequential steps when receiving Roundcube database connection tickets:
- [ ] Phase 1: Log Collection
- Check
/var/cpanel/roundcube/logs/errors.logor local user logs in/home/USER/etc/domain.com/roundcube/errors.log. - [ ] Phase 2: Space and Inodes
- Verify disk space and inodes with
df -handdf -ion/homeand/tmp. - Ensure
/tmphas1777permissions. - [ ] Phase 3: Backup and Permissions
- Create a backup of the SQLite database in
/root/roundcube-backup-.... - Fix permissions and ownership with
chown -R USER:mail /home/USER/etc. - [ ] Phase 4: SQLite Integrity
- Run
sqlite3 [database] "PRAGMA integrity_check;". - If corrupted, rename the file to force a clean regeneration.
- [ ] Phase 5: Jailing and Security
- On CloudLinux, run
cagefsctl --remount USER. - On SELinux, run
setenforce 0for testing, restore withsetenforce 1, and fix context usingrestorecon.
12. Automated diagnostic script (diagnose-roundcube-db14.sh)#
To streamline Level 2/3 support troubleshooting, run this diagnostic script on the server to automatically map all described layers and pinpoint potential root causes of the error:
#!/bin/bash
# diagnose-roundcube-db14.sh
# Diagnostic script for Roundcube cPanel DB Error [14].
# Must be executed as root.
set -euo pipefail
# Color configuration for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0;37m' # No Color
log_info() {
echo -e "[${GREEN}INFO${NC}] $1"
}
log_warn() {
echo -e "[${YELLOW}WARN${NC}] $1"
}
log_error() {
echo -e "[${RED}ERROR${NC}] $1"
}
# 1. Verify root privileges
if [ "$EUID" -ne 0 ]; then
log_error "This script must be executed as root."
exit 1
fi
log_info "Starting Roundcube DB Error [14] diagnostics..."
# Arguments: cPanel user and/or optional email
CP_USER=""
EMAIL=""
if [ $# -ge 1 ]; then
CP_USER="$1"
fi
if [ $# -ge 2 ]; then
EMAIL="$2"
fi
# 2. System and Panel Information
log_info "--- System and Panel Versions ---"
if [ -f /usr/local/cpanel/version ]; then
CP_VER=$(cat /usr/local/cpanel/version)
log_info "cPanel Version: $CP_VER"
else
log_warn "cPanel not detected or version file inaccessible."
fi
RCUBE_PATH="/usr/local/cpanel/base/3rdparty/roundcube"
if [ -d "$RCUBE_PATH" ]; then
if [ -f "$RCUBE_PATH/program/include/rcube.php" ]; then
RC_VER=$(grep -i "RCMAIL_VERSION" "$RCUBE_PATH/program/include/rcube.php" || grep -i "version" "$RCUBE_PATH/program/include/rcube.php" | head -n 1)
log_info "Roundcube Version: $RC_VER"
fi
else
log_warn "cPanel Roundcube directory not found."
fi
# 3. Verify SQLite PHP Extensions
log_info "--- PHP SQLite Extensions ---"
if command -v php >/dev/null 2>&1; then
PHP_OPTS=$(php -m)
if echo "$PHP_OPTS" | grep -iq "sqlite3" && echo "$PHP_OPTS" | grep -iq "pdo_sqlite"; then
log_info "sqlite3 and pdo_sqlite extensions are loaded in global PHP."
else
log_warn "sqlite3 or pdo_sqlite extensions are missing in global PHP."
fi
else
log_warn "php binary not found in global PATH."
fi
# 4. Check Disk Space and Inodes on Critical Partitions
log_info "--- Disk Space and Inodes ---"
check_partition() {
local part="$1"
if df -P "$part" >/dev/null 2>&1; then
local space_pct=$(df -P "$part" | awk 'NR==2 {print $5}' | sed 's/%//')
local inode_pct=$(df -iP "$part" | awk 'NR==2 {print $5}' | sed 's/%//')
log_info "Partition $part: Space Usage: ${space_pct}%, Inode Usage: ${inode_pct}%"
if [ "$space_pct" -gt 95 ]; then
log_error "Critical space usage on partition $part (>95%): $space_pct%"
fi
if [ "$inode_pct" -gt 95 ]; then
log_error "Critical inode usage on partition $part (>95%): $inode_pct%"
fi
else
log_warn "Could not check partition for $part"
fi
}
check_partition "/home"
check_partition "/tmp"
# Check /tmp Sticky Bit
TMP_PERM=$(stat -c "%a" /tmp 2>/dev/null || stat -c "%A" /tmp 2>/dev/null)
if [[ "$TMP_PERM" == *"1777"* || "$TMP_PERM" == *"rwxrwxrwt"* || "$TMP_PERM" == *"777"* ]]; then
log_info "/tmp permissions: $TMP_PERM (Sticky Bit correct)"
else
log_warn "/tmp permissions unusual: $TMP_PERM (should be 1777 / drwxrwxrwt)"
fi
# 5. Filesystem Read-Only State Check
log_info "--- Filesystem State ---"
if mount | grep -q "on /home .*\(ro\)"; then
log_error "Partition /home is mounted as READ-ONLY (ro)!"
else
log_info "Partition /home mounted as read-write."
fi
# 6. CloudLinux and CageFS
log_info "--- CloudLinux and CageFS ---"
if [ -f /etc/redhat-release ] && grep -qi "cloudlinux" /etc/redhat-release; then
log_info "OS: CloudLinux detected."
if command -v cagefsctl >/dev/null 2>&1; then
CAGE_VER=$(cagefsctl --version 2>/dev/null || echo "Unknown")
log_info "CageFS active: $CAGE_VER"
if [ -n "$CP_USER" ]; then
if cagefsctl --list-users | grep -qw "$CP_USER"; then
log_info "User '$CP_USER' is jailed inside CageFS."
else
log_warn "User '$CP_USER' is NOT jailed inside CageFS."
fi
fi
else
log_warn "cagefsctl not found in system."
fi
else
log_info "CloudLinux not detected."
fi
# 7. SELinux Status
log_info "--- SELinux ---"
if command -v getenforce >/dev/null 2>&1; then
SEL_STATUS=$(getenforce)
log_info "SELinux Status: $SEL_STATUS"
if [ "$SEL_STATUS" = "Enforcing" ]; then
log_warn "SELinux is in Enforcing mode. Ensure proper contexts on the email etc directory."
fi
else
log_info "SELinux not active/installed."
fi
# 8. Map and Audit Roundcube SQLite Databases
log_info "--- Auditing Roundcube SQLite Databases ---"
find_dbs() {
local search_path="$1"
find "$search_path" -type f -name "*.rcube.db" 2>/dev/null
}
DB_FILES=""
if [ -n "$CP_USER" ]; then
USER_HOME="/home/$CP_USER"
if [ -d "$USER_HOME" ]; then
log_info "Mapping databases for user: $CP_USER in $USER_HOME"
if command -v quota >/dev/null 2>&1; then
quota -vs "$CP_USER" 2>&1 | head -n 5 || true
fi
DB_FILES=$(find_dbs "$USER_HOME")
else
log_error "User home directory not found: $USER_HOME"
exit 1
fi
else
log_info "No user specified. Scanning entire /home directory..."
DB_FILES=$(find_dbs "/home")
fi
if [ -z "$DB_FILES" ]; then
log_warn "No .rcube.db files found in search path."
else
for db in $DB_FILES; do
if [ -n "$EMAIL" ] && [[ "$db" != *"$EMAIL"* ]]; then
continue
fi
log_info "Analyzing database: $db"
local owner=$(stat -c '%U:%G' "$db")
local perm=$(stat -c '%a' "$db")
local size=$(stat -c '%s' "$db")
log_info " Owner/Group: $owner | Permissions: $perm | Size: $size bytes"
local user_from_path=$(echo "$db" | cut -d'/' -f3)
if [ "$owner" != "$user_from_path:mail" ] && [ "$owner" != "$user_from_path:nobody" ]; then
log_error " [Permission] Incorrect owner! Expected '$user_from_path:mail' or '$user_from_path:nobody', found '$owner'."
fi
if [ "$size" -eq 0 ]; then
log_error " [Integrity] Database file size is ZERO (0 bytes)."
fi
if command -v sqlite3 >/dev/null 2>&1; then
local check_res=$(sqlite3 "$db" "PRAGMA integrity_check;" 2>&1 || echo "Execution Error")
if [ "$check_res" = "ok" ]; then
log_info " [Integrity] SQLite PRAGMA integrity_check: OK"
else
log_error " [Integrity] Physical corruption detected: $check_res"
fi
else
log_warn " sqlite3 CLI not installed. Skipping pragma integrity check."
fi
if [ -f "${db}-journal" ]; then
log_warn " [Locks] Active Journal temp file: ${db}-journal"
fi
if [ -f "${db}-wal" ]; then
log_warn " [Locks] Active Write-Ahead Log temp file: ${db}-wal"
fi
done
fi
log_info "Diagnostics completed."
Was this article helpful?
Leave a quick reaction to help prioritize future technical guides:
This post is licensed under CC BY-NC.



Comments
Join the discussion below.
0 comments