-
-
Save imannms000/69e6a8c4d19032a2ad7a529b3cdc3431 to your computer and use it in GitHub Desktop.
| #!/bin/bash | |
| #=============================================================================== | |
| # Production-Hardened MySQL 8.0 Installer for Ubuntu 20.04/22.04/24.04 | |
| # Idempotent | Failsafe | SOC2/ISO27001-aligned | |
| #=============================================================================== | |
| set -euo pipefail | |
| IFS=$'\n\t' | |
| #------------------------------------------------------------------------------- | |
| # CONFIGURATION | |
| #------------------------------------------------------------------------------- | |
| readonly SCRIPT_NAME="$(basename "$0")" | |
| readonly LOG_FILE="/var/log/${SCRIPT_NAME%.sh}.log" | |
| readonly BACKUP_DIR="/root/mysql-backup-$(date +%Y%m%d-%H%M%S)" | |
| readonly MYSQL_CONFIG_DIR="/etc/mysql/mysql.conf.d" | |
| readonly MYSQLD_CNF="${MYSQL_CONFIG_DIR}/99-production-hardening.cnf" | |
| # Minimum system requirements | |
| readonly MIN_RAM_MB=1024 | |
| readonly MIN_DISK_MB=2048 | |
| #------------------------------------------------------------------------------- | |
| # LOGGING | |
| #------------------------------------------------------------------------------- | |
| exec > >(tee -a "${LOG_FILE}") 2>&1 | |
| log() { | |
| echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | |
| } | |
| die() { | |
| log "❌ FATAL: $*" | |
| exit 1 | |
| } | |
| #------------------------------------------------------------------------------- | |
| # PREFLIGHT CHECKS | |
| #------------------------------------------------------------------------------- | |
| preflight() { | |
| log "🔍 Running pre-flight checks..." | |
| # Root check | |
| if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then | |
| die "This script must be run as root or with sudo." | |
| fi | |
| # OS check | |
| if ! grep -qE 'Ubuntu (20|22|24)\.04' /etc/os-release 2>/dev/null; then | |
| log "⚠️ Warning: This script is tested on Ubuntu 20.04/22.04/24.04 only." | |
| read -r -p "Continue anyway? [y/N]: " confirm | |
| [[ "$confirm" =~ ^[Yy]$ ]] || die "Aborted by user." | |
| fi | |
| # RAM check | |
| local avail_ram | |
| avail_ram=$(free -m | awk '/^Mem:/{print $7}') | |
| if [[ "${avail_ram}" -lt "${MIN_RAM_MB}" ]]; then | |
| die "Insufficient available RAM: ${avail_ram}MB (min: ${MIN_RAM_MB}MB)" | |
| fi | |
| # Disk check | |
| local avail_disk | |
| avail_disk=$(df -m / | awk 'NR==2 {print $4}') | |
| if [[ "${avail_disk}" -lt "${MIN_DISK_MB}" ]]; then | |
| die "Insufficient disk space on /: ${avail_disk}MB (min: ${MIN_DISK_MB}MB)" | |
| fi | |
| # Check for MariaDB conflict | |
| if dpkg -l | grep -qE '^ii\s+mariadb'; then | |
| die "MariaDB is installed. MySQL and MariaDB conflict. Remove MariaDB first." | |
| fi | |
| log "✅ Pre-flight checks passed." | |
| } | |
| #------------------------------------------------------------------------------- | |
| # PASSWORD INPUT WITH STRENGTH VALIDATION | |
| #------------------------------------------------------------------------------- | |
| prompt_password() { | |
| local var_name="$1" | |
| local prompt_text="$2" | |
| local confirm_text="$3" | |
| local min_length="${4:-16}" | |
| local pass1 pass2 | |
| while true; do | |
| read -r -s -p "${prompt_text}: " pass1 | |
| echo "" | |
| read -r -s -p "${confirm_text}: " pass2 | |
| echo "" | |
| if [[ -z "$pass1" ]]; then | |
| log "⚠️ Password cannot be empty." | |
| continue | |
| fi | |
| if [[ "${#pass1}" -lt "${min_length}" ]]; then | |
| log "⚠️ Password must be at least ${min_length} characters." | |
| continue | |
| fi | |
| if [[ "$pass1" != "$pass2" ]]; then | |
| log "❌ Passwords do not match." | |
| continue | |
| fi | |
| # Basic entropy check | |
| if ! [[ "$pass1" =~ [A-Z] && "$pass1" =~ [a-z] && "$pass1" =~ [0-9] && "$pass1" =~ [[:punct:]] ]]; then | |
| log "⚠️ Password must contain uppercase, lowercase, digit, and special character." | |
| continue | |
| fi | |
| printf -v "$var_name" '%s' "$pass1" | |
| break | |
| done | |
| } | |
| #------------------------------------------------------------------------------- | |
| # INSTALLATION (Idempotent: Skips if package is installed) | |
| #------------------------------------------------------------------------------- | |
| install_mysql() { | |
| if dpkg -l | grep -qE '^ii\s+mysql-server'; then | |
| log "📦 MySQL server package is already installed. Skipping installation step." | |
| return 0 | |
| fi | |
| log "🛠️ Installing MySQL Server..." | |
| export DEBIAN_FRONTEND=noninteractive | |
| apt-get update -y | |
| local mysql_version | |
| mysql_version=$(apt-cache policy mysql-server 2>/dev/null | grep Candidate | awk '{print $2}' | cut -d'-' -f1 | cut -d':' -f2 | cut -d'.' -f1-2 || echo "0") | |
| if [[ "$mysql_version" == "8.0" ]]; then | |
| log "📦 Using distribution-provided MySQL 8.0..." | |
| apt-get install -y mysql-server mysql-client | |
| else | |
| log "📥 Adding official MySQL APT repository..." | |
| local apt_config_url | |
| apt_config_url=$(curl -s https://dev.mysql.com/downloads/repo/apt/ | grep -oP 'https://dev.mysql.com/get/mysql-apt-config_[^"]+_all.deb' | head -1) | |
| if [[ -z "$apt_config_url" ]]; then | |
| die "Could not determine latest mysql-apt-config URL." | |
| fi | |
| local deb_file="/tmp/$(basename "$apt_config_url")" | |
| wget -q "$apt_config_url" -O "$deb_file" || die "Failed to download MySQL APT config" | |
| echo "mysql-apt-config mysql-apt-config/select-server select mysql-8.0" | debconf-set-selections | |
| dpkg -i "$deb_file" || apt-get install -f -y | |
| rm -f "$deb_file" | |
| apt-get update -y | |
| apt-get install -y mysql-server mysql-client | |
| fi | |
| systemctl enable mysql | |
| systemctl start mysql | |
| local wait_count=0 | |
| until mysqladmin ping --silent 2>/dev/null || [[ $wait_count -ge 30 ]]; do | |
| sleep 2 | |
| ((wait_count++)) | |
| done | |
| if ! mysqladmin ping --silent 2>/dev/null; then | |
| die "MySQL failed to start within 60 seconds. Check /var/log/mysql/error.log" | |
| fi | |
| log "✅ MySQL installed and running." | |
| } | |
| #------------------------------------------------------------------------------- | |
| # SECURITY HARDENING (Idempotent: Uses conditional checks per command) | |
| #------------------------------------------------------------------------------- | |
| harden_mysql() { | |
| log "🔒 Applying security hardening..." | |
| local root_pass="$1" | |
| local admin_user="$2" | |
| local admin_pass="$3" | |
| local sql_file | |
| sql_file=$(mktemp /tmp/mysql-harden-XXXXXX.sql) | |
| chmod 600 "$sql_file" | |
| cat > "$sql_file" <<EOF | |
| -- Safely alter root auth if not already done | |
| ALTER USER IF EXISTS 'root'@'localhost' IDENTIFIED WITH caching_sha2_password BY '${root_pass}'; | |
| -- Create dedicated admin user if missing, or update its credentials safely | |
| CREATE USER IF NOT EXISTS '${admin_user}'@'localhost' IDENTIFIED WITH caching_sha2_password BY '${admin_pass}'; | |
| ALTER USER '${admin_user}'@'localhost' IDENTIFIED WITH caching_sha2_password BY '${admin_pass}'; | |
| GRANT ALL PRIVILEGES ON *.* TO '${admin_user}'@'localhost' WITH GRANT OPTION; | |
| -- Remove anonymous users | |
| DELETE FROM mysql.user WHERE User=''; | |
| -- Remove test database | |
| DROP DATABASE IF EXISTS test; | |
| DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%'; | |
| -- Remove remote root access | |
| DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1'); | |
| -- Swap root to auth_socket for local system administration convenience & security | |
| ALTER USER IF EXISTS 'root'@'localhost' IDENTIFIED WITH auth_socket; | |
| ALTER USER IF EXISTS '${admin_user}'@'localhost' PASSWORD EXPIRE NEVER; | |
| FLUSH PRIVILEGES; | |
| EOF | |
| # Execute SQL depending on current root authentication status | |
| if mysql -e "SELECT 1" &>/dev/null; then | |
| mysql < "$sql_file" | |
| else | |
| # Fallback if execution requires systemic root authentication pass | |
| mysql -u root -p"${root_pass}" --batch < "$sql_file" 2>/dev/null || mysql -u root --batch < "$sql_file" | |
| fi | |
| rm -f "$sql_file" | |
| # Fixed: Load timezone data natively using host zoneinfo maps safely | |
| log "🌍 Verifying system timezone tables inside MySQL..." | |
| mysql_tzinfo_to_sql /usr/share/zoneinfo 2>/dev/null | mysql -u root mysql || log "⚠️ Timezone population skipped or already processed." | |
| # Store credentials securely using mysql_config_editor | |
| local login_path="production_admin" | |
| mysql_config_editor remove --login-path="${login_path}" 2>/dev/null || true | |
| mysql_config_editor set --login-path="${login_path}" --host=localhost --user="${admin_user}" --password <<< "${admin_pass}" | |
| log "✅ Security hardening complete." | |
| } | |
| #------------------------------------------------------------------------------- | |
| # PRODUCTION CONFIGURATION | |
| #------------------------------------------------------------------------------- | |
| configure_production() { | |
| log "⚙️ Applying production configuration..." | |
| mkdir -p "${MYSQL_CONFIG_DIR}" | |
| cat > "${MYSQLD_CNF}" <<'EOF' | |
| [mysqld] | |
| # Network Security | |
| bind-address = 127.0.0.1 | |
| mysqlx-bind-address = 127.0.0.1 | |
| skip-symbolic-links | |
| local_infile = 0 | |
| # Authentication | |
| default_authentication_plugin = caching_sha2_password | |
| # Logging (for audit and debugging) | |
| log_error = /var/log/mysql/error.log | |
| general_log = OFF | |
| slow_query_log = ON | |
| slow_query_log_file = /var/log/mysql/slow.log | |
| long_query_time = 2 | |
| # Binary logging (required for replication and point-in-time recovery) | |
| server-id = 1 | |
| log_bin = /var/log/mysql/mysql-bin | |
| binlog_expire_logs_seconds = 604800 # 7 days | |
| max_binlog_size = 100M | |
| # InnoDB Tuning (conservative defaults, adjust for your RAM) | |
| innodb_buffer_pool_size = 256M | |
| innodb_log_file_size = 64M | |
| innodb_flush_log_at_trx_commit = 2 | |
| innodb_flush_method = O_DIRECT | |
| # Connection limits | |
| max_connections = 151 | |
| wait_timeout = 28800 | |
| interactive_timeout = 28800 | |
| # Charset | |
| character-set-server = utf8mb4 | |
| collation-server = utf8mb4_0900_ai_ci | |
| # Security | |
| require_secure_transport = OFF # Set ON after SSL certs are configured | |
| EOF | |
| mkdir -p /var/log/mysql | |
| chown mysql:mysql /var/log/mysql | |
| chmod 750 /var/log/mysql | |
| chmod 644 "${MYSQLD_CNF}" | |
| # Restart gracefully to implement rules | |
| systemctl restart mysql | |
| local wait_count=0 | |
| until mysqladmin ping --silent 2>/dev/null || [[ $wait_count -ge 30 ]]; do | |
| sleep 2 | |
| ((wait_count++)) | |
| done | |
| if ! mysqladmin ping --silent 2>/dev/null; then | |
| die "MySQL failed to restart after configuration changes." | |
| fi | |
| log "✅ Production configuration applied." | |
| } | |
| #------------------------------------------------------------------------------- | |
| # SYSTEM HARDENING | |
| #------------------------------------------------------------------------------- | |
| harden_system() { | |
| log "🛡️ Applying system-level hardening..." | |
| if ufw status | grep -q "Status: active"; then | |
| ufw deny 3306/tcp || true | |
| ufw allow from 127.0.0.1 to any port 3306 proto tcp || true | |
| log "✅ UFW rule verified: 3306 restricted to localhost." | |
| fi | |
| cat > /etc/logrotate.d/mysql-custom <<'EOF' | |
| /var/log/mysql/*.log { | |
| daily | |
| rotate 14 | |
| compress | |
| missingok | |
| notifempty | |
| create 640 mysql adm | |
| sharedscripts | |
| postrotate | |
| test -x /usr/bin/mysqladmin || exit 0 | |
| if /usr/bin/mysqladmin ping > /dev/null 2>&1; then | |
| /usr/bin/mysqladmin flush-logs | |
| fi | |
| endscript | |
| } | |
| EOF | |
| if [[ -f /etc/apt/apt.conf.d/50unattended-upgrades ]]; then | |
| if ! grep -q "mysql" /etc/apt/apt.conf.d/50unattended-upgrades; then | |
| sed -i 's/Unattended-Upgrade::Allowed-Origins {/Unattended-Upgrade::Allowed-Origins {\n\t\t"LP-PPA-mysql-8.0:${distro_codename}";\n\t\t"Ubuntu:${distro_codename}-security";/' /etc/apt/apt.conf.d/50unattended-upgrades || true | |
| fi | |
| fi | |
| chmod 750 /var/lib/mysql | |
| chown -R mysql:mysql /var/lib/mysql | |
| log "✅ System hardening complete." | |
| } | |
| #------------------------------------------------------------------------------- | |
| # POST-INSTALL VERIFICATION | |
| #------------------------------------------------------------------------------- | |
| verify_installation() { | |
| log "🔍 Running post-install verification..." | |
| local admin_user="$1" | |
| systemctl is-active --quiet mysql || die "MySQL service is not active." | |
| ss -tlnp | grep -q "127.0.0.1:3306" || die "MySQL not bound to 127.0.0.1:3306" | |
| local root_auth | |
| root_auth=$(mysql -u root -e "SELECT plugin FROM mysql.user WHERE user='root' AND host='localhost';" 2>/dev/null | tail -1 || true) | |
| if [[ "$root_auth" != "auth_socket" ]]; then | |
| log "⚠️ Warning: root@localhost is not using auth_socket." | |
| fi | |
| local admin_count | |
| admin_count=$(mysql -u root -e "SELECT COUNT(*) FROM mysql.user WHERE user='${admin_user}' AND host='localhost';" 2>/dev/null | tail -1 || true) | |
| if [[ "$admin_count" != "1" ]]; then | |
| die "Admin user '${admin_user}' was not created successfully." | |
| fi | |
| local anon_count | |
| anon_count=$(mysql -u root -e "SELECT COUNT(*) FROM mysql.user WHERE user='';" 2>/dev/null | tail -1 || true) | |
| if [[ "$anon_count" != "0" ]]; then | |
| die "Anonymous users still exist." | |
| fi | |
| log "✅ All verification checks passed." | |
| } | |
| #------------------------------------------------------------------------------- | |
| # MAIN | |
| #------------------------------------------------------------------------------- | |
| main() { | |
| log "====================================================" | |
| log " 🛡️ Production MySQL 8.0 Installer (Idempotent)" | |
| log "====================================================" | |
| preflight | |
| echo "" | |
| log "🔐 Configure MySQL root password..." | |
| prompt_password MY_ROOT_PASS "Enter strong root password" "Confirm root password" 16 | |
| echo "" | |
| log "👤 Configure dedicated Admin User..." | |
| while true; do | |
| read -r -p "Enter admin username [admin]: " ADMIN_USER | |
| ADMIN_USER=${ADMIN_USER:-admin} | |
| if [[ -z "$ADMIN_USER" || "$ADMIN_USER" == "root" ]]; then | |
| log "⚠️ Invalid username. Cannot be empty or 'root'." | |
| elif [[ "$ADMIN_USER" =~ [^a-zA-Z0-9_] ]]; then | |
| log "⚠️ Username must contain only letters, numbers, and underscores." | |
| else | |
| break | |
| fi | |
| done | |
| prompt_password ADMIN_PASS "Enter admin password" "Confirm admin password" 16 | |
| if [[ "$MY_ROOT_PASS" == "$ADMIN_PASS" ]]; then | |
| die "Root and admin passwords must be different." | |
| fi | |
| install_mysql | |
| harden_mysql "$MY_ROOT_PASS" "$ADMIN_USER" "$ADMIN_PASS" | |
| configure_production | |
| harden_system | |
| verify_installation "$ADMIN_USER" | |
| MY_ROOT_PASS="CLEARED" | |
| ADMIN_PASS="CLEARED" | |
| unset MY_ROOT_PASS ADMIN_PASS | |
| log "" | |
| log "====================================================" | |
| log "🎉 MySQL Configuration Maintained/Deployed successfully!" | |
| log "" | |
| log "🔐 ROOT ACCESS:" | |
| log " sudo mysql # OS auth only" | |
| log " (Network root auth is DISABLED)" | |
| log "" | |
| log "👤 ADMIN ACCESS:" | |
| log " mysql -u ${ADMIN_USER} -p" | |
| log "" | |
| log "📁 LOGS:" | |
| log " ${LOG_FILE}" | |
| log " /var/log/mysql/error.log" | |
| log "" | |
| log "⚠️ NEXT STEPS:" | |
| log " 1. Configure SSL: set require_secure_transport=ON after placing certs" | |
| log " 2. Adjust innodb_buffer_pool_size to 70% of RAM in:" | |
| log " ${MYSQLD_CNF}" | |
| log " 3. Set up automated backups (e.g., Percona XtraBackup)" | |
| log " 4. Configure monitoring (Prometheus mysqld_exporter / Datadog)" | |
| log "====================================================" | |
| } | |
| main "$@" |
Advanced Guide
1. WSL2-Specific Setup Guide
Option A: Native systemd (Recommended for Windows 11 22H2+ / Windows 10 KB5020030+)
# 1. Check WSL version
wsl --version
# Required: WSL version >= 0.67.6
# 2. Enable systemd in WSL
sudo tee /etc/wsl.conf <<EOF
[boot]
systemd=true
EOF
# 3. Shutdown and restart WSL
wsl --shutdown
# Then reopen Ubuntu from Start MenuMicrosoft officially supports systemd in WSL2 as of version 0.67.6. This is the default for Ubuntu installed via
wsl --install.
Option B: SysVinit Fallback (Older WSL / Windows 10)
The script auto-detects this and installs a helper. Add auto-start to your shell:
# Add to ~/.bashrc
if grep -qi microsoft /proc/version 2>/dev/null; then
sudo /usr/local/bin/wsl-mysql start >/dev/null 2>&1
fiOption C: Windows Task Scheduler (For true auto-start)
# PowerShell (as Administrator)
$action = New-ScheduledTaskAction -Execute "wsl.exe" -Argument "-d Ubuntu-20.04 -u root /usr/local/bin/wsl-mysql start"
$trigger = New-ScheduledTaskTrigger -AtLogOn
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries
Register-ScheduledTask -TaskName "WSL2-MySQL-Start" -Action $action -Trigger $trigger -Settings $settings -RunLevel Highest2. Operational Runbook
Daily Operations
# Check MySQL status
sudo systemctl status mysql # systemd
sudo wsl-mysql status # WSL2 fallback
# Connect as admin
mysql --login-path=production_admin
# Or with password prompt (if login-path not set)
mysql -u admin -p
# View logs
sudo tail -f /var/log/mysql/error.log
sudo tail -f /var/log/mysql/slow.log
# Check binary logs
mysql -u root -e "SHOW BINARY LOGS;"Backup Procedures
# Physical backup (Percona XtraBackup — recommended for production)
sudo apt install percona-xtrabackup-80
sudo xtrabackup --backup --target-dir=/backup/$(date +%F)
# Logical backup (mysqldump — for smaller datasets or specific DBs)
mysqldump --login-path=production_admin --all-databases --single-transaction \
--routines --triggers --events | gzip > /backup/full-$(date +%F).sql.gzRecovery Procedures
# Restore from physical backup
sudo systemctl stop mysql
sudo rm -rf /var/lib/mysql/*
sudo xtrabackup --prepare --target-dir=/backup/2024-01-15
sudo xtrabackup --copy-back --target-dir=/backup/2024-01-15
sudo chown -R mysql:mysql /var/lib/mysql
sudo systemctl start mysql
# Restore from logical backup
zcat /backup/full-2024-01-15.sql.gz | mysql -u rootPassword Rotation
# Admin password rotation
mysql -u root -e "ALTER USER 'admin'@'localhost' IDENTIFIED BY 'NewStr0ng!Pass';"
# Update login-path
mysql_config_editor set --login-path=production_admin --host=localhost --user=admin --password3. Troubleshooting Matrix
| Symptom | Cause | Resolution |
|---|---|---|
MySQL failed to start within 60s |
/var/run/mysqld missing (WSL2) |
sudo mkdir -p /var/run/mysqld && sudo chown mysql:mysql /var/run/mysqld |
ERROR 2002 (HY000): Can't connect |
Service not running | sudo wsl-mysql start or sudo systemctl start mysql |
Access denied for user 'root'@'localhost' |
Using password instead of OS auth | Run sudo mysql (no password) |
Port 3306 already in use |
Existing MySQL or other process | sudo ss -tlnp | grep 3306 to identify; backup and re-run script |
systemctl: command not found |
WSL2 without systemd | Use sudo wsl-mysql start or enable systemd in /etc/wsl.conf |
Binary logging not working |
Log directory permissions | sudo chown mysql:mysql /var/log/mysql |
4. Configuration Reference
| File | Purpose | Permissions |
|---|---|---|
/etc/mysql/mysql.conf.d/99-production-hardening.cnf |
Production tuning | 644 |
/var/log/mysql/error.log |
Error log | 640 |
/var/log/mysql/slow.log |
Slow query log | 640 |
/var/log/mysql/mysql-bin.* |
Binary logs | 640 |
/var/lib/mysql/ |
Data directory | 750 |
/root/.mylogin.cnf |
Encrypted credentials (login-path) | 600 |
5. WSL2 Known Limitations
| Limitation | Workaround |
|---|---|
No systemd on older WSL |
Use wsl-mysql helper script |
/var/run is tmpfs (cleared on shutdown) |
Helper script pre-creates /var/run/mysqld |
| No true "boot" — WSL starts on demand | Windows Task Scheduler or .bashrc trigger |
| Windows Firewall may block 3306 | Bound to 127.0.0.1 only (not exposed to Windows) |
| Case sensitivity differs from Windows | lower_case_table_names=0 (Linux default) |
6. Compliance Checklist
- Passwords: 16+ chars, mixed case, digits, special characters
- Root:
auth_socketonly, no TCP password auth - Network:
bind-address = 127.0.0.1, no remote root - Anonymous users: Removed
- Test database: Dropped
- Binary logging: Enabled
- SSL: Configure certificates and set
require_secure_transport=ON - Backups: Automated daily physical + weekly logical
- Monitoring:
mysqld_exporteror Datadog agent - Log retention: 14 days via logrotate
- Patch management: Unattended security updates enabled
- Documentation: This runbook printed and accessible
Connecting Securely via DBeaver
1. Local
MySQL 8.0 uses a highly secure authentication plugin (caching_sha2_password) by default, which is exactly what your hardening script configured.
When you try to connect via DBeaver locally, MySQL requires an encrypted password transfer. If DBeaver doesn't have the server's public key cached locally yet, it tries to fetch it from the server. Because your script locked down connections, MySQL aggressively blocks this raw public key request for security, resulting in the "Public Key Retrieval is not allowed" error.
Fortunately, you don't need to change anything on your server to fix this. You just need to flip one toggle inside DBeaver.
How to Fix It in DBeaver
Assuming you already created MySQL connection on DBeaver.
- Open DBeaver.
- Right-click your MySQL connection in the left-hand navigation panel and select Edit Connection.
- In the settings window that opens, look at the left sidebar and click on Connection settings -> Driver properties.
- In the main panel, you will see a long, alphabetized list of properties. Scroll down (or use the filter box at the top) to find
allowPublicKeyRetrieval. - Click on the value column next to it (which is currently blank or
false) and change it totrue. - While you are there, look right above or below it for
useSSLand ensure it is set tofalse(since your script currently hasrequire_secure_transport = OFFuntil you install real SSL certificates). - Click Test Connection at the bottom.
2. Remote
Because your database is locked tightly to 127.0.0.1 and protected by a local firewall rule, you cannot point DBeaver directly at your server's public IP address. Instead, you must bridge into the server securely using an SSH Tunnel.
Connection Settings Matrix
Create a new MySQL connection profile inside DBeaver and complete the following two configuration tabs:
1. Main Connection Settings Tab
| Parameter | Setting | Context |
|---|---|---|
| Host | localhost |
Crucial: Do not put your server IP here. Leave it exactly as localhost because DBeaver will evaluate this path from inside the server after tunneling. |
| Port | 3306 |
Default MySQL communications port. |
| Database | mysql |
The core administrative system schema. |
| Username | admin |
The custom administrative user created during execution. |
| Password | •••••••••••• |
The corresponding admin password. |
2. SSH Connection Tab
Check the box labeled "Use SSH Tunnel" and fill out your host environment properties:
| Parameter | Setting |
|---|---|
| Connection Method | Key Pair (Recommended) or Password |
| Host/IP | Your server's actual public-facing IP address. |
| Port | 22 (Or your custom SSH server port). |
| Username | Your server's SSH login user (e.g., root or ubuntu). |
| Passphrase / Password | Your system account SSH passphrase or password. |
Click Test Connection at the bottom of the DBeaver configuration prompt. The terminal interface will securely piggyback across your encrypted SSH boundary and initialize your database session safely.
// secured for documentation
// secured for documentation
// secured for documentation
// secured for documentation
// secured for documentation
Production MySQL 8.0 Deployment Documentation
Target Systems: Ubuntu 20.04 / 22.04 / 24.04
Architecture Standards: Idempotent, Failsafe, SOC2/ISO27001-Aligned
Architectural Overview
This automated script deploys a production-ready, locked-down instance of MySQL 8.0. Rather than simply installing software, it implements enterprise-grade configuration standards, tightly restricts networking, isolates access privileges, and sets up robust system maintenance hooks.
Core Security Architecture
127.0.0.1. The Ubuntu Firewall (UFW) explicitly denies public traffic on port3306and only permits loopback connections.rootdatabase account is stripped of remote access and locked to the operating system's nativeauth_socket. This means the database root can only be accessed by logging into the OS as root/sudo and runningsudo mysql. Traditional password authentication for root is entirely disabled over the network.admin) is generated with a non-expiring, highly secure password usingcaching_sha2_passwordmechanics.mysql_config_editor. Administrative login tokens are encrypted locally inside~/.mylogin.cnf, preventing plain-text password leakage in shell histories or process lists.Deployment Guide
Step 1: Resource Verification
Before execution, verify that your host machine meets the script's strict pre-flight requirements:
/.You can check your available memory assets by running:
Step 2: Saving the Installer Script
Create a new file on your server and paste the script contents into it:
Paste the complete script code into the text editor. Save and exit by pressing Ctrl+O, Enter, then Ctrl+X.
Step 3: Setting Permissions & Execution
Grant execution rights to the file and run it under
sudoprivileges:Step 4: Responding to Interactive Prompts
During execution, the script will halt for critical parameter inputs:
adminprofile name, or type a custom string (e.g.,db_manager).Verifying system timezone tables inside MySQL..., the terminal will prompt you:Type your Admin Password here and press Enter. This explicitly authorizes
mysql_config_editorto encrypt your admin token into your system profile shell.🔑 Database Maintenance & Connection Management
Local Administration (Command Line)
Because the script aligns with strict privilege isolation rules, logging into your database locally requires specific syntax paths:
*(Replace
adminwith your custom username if you modified it during setup).Post-Installation Verification Checklist
The script executes a self-test array upon finalizing operations, but you should verify these locations manually to confirm a successful deployment:
/var/log/install-mysql.log./etc/mysql/mysql.conf.d/99-production-hardening.cnf./etc/logrotate.d/mysql-custom. Check system validity by running: