[{"data":1,"prerenderedAt":153},["ShallowReactive",2],{"seo-verification":3,"blog-hosting-mysql-on-a-vps-ma-en":6,"promo-bar-likely":152},{"google":4,"bing":5},"EycwPY2XMyTkVzas3n1ygeNJFGAH513qrMjfDljzsMQ","",{"key":7,"data":8},"blog-hosting-mysql-on-a-vps-ma-en",{"id":9,"slug":10,"slugs":11,"title":15,"excerpt":16,"readTime":17,"views":18,"isPinned":19,"noindex":19,"publishedAt":20,"updatedAt":21,"category":22,"categories":28,"featuredImage":30,"bgImage":31,"posterImage":32,"relatedSolution":33,"intro":36,"sections":37,"ctaTitle":95,"ctaBody":96,"ctaButton":97,"ctaUrl":98,"relatedPosts":99},57,"hosting-mysql-on-a-vps",{"fr":12,"en":10,"ar":13,"es":14},"heberger-mysql-vps","استضافة-mysql-على-خادم-vps","alojar-mysql-en-un-vps","Hosting MySQL on a VPS: complete guide","Install MySQL 8.4 LTS on a VPS: prerequisites, security hardening, InnoDB performance, secure remote access, automated backups, and troubleshooting.",9,0,false,"2026-04-24T00:00:00+00:00","2026-11-09T20:55:18+00:00",{"id":23,"name":24,"slug":25,"color":26,"icon":27},6,"Databases","bases-de-donnees","bg-teal-500\u002F10 text-teal-400","database",[29],{"id":23,"name":24,"slug":25,"color":26,"icon":27},null,"\u002Fblog\u002Fcovers\u002Fbg.svg","\u002Fblog\u002Fcovers\u002Fheberger-mysql-vps-poster.svg",{"categorySlug":34,"appSlug":35},"databases","mysql-stack","MySQL powers a huge part of the web: WordPress, Magento, Laravel, and thousands of PHP applications. Self-hosting it on a VPS gives you dedicated performance, full control over the InnoDB configuration, and predictable costs, far from the limits of shared hosting.",[38,42,52,55,77,80,83,86,89,92],{"type":39,"title":40,"body":41},"h2","Why self-host MySQL on a VPS","On shared hosting, your MySQL database shares RAM and I\u002FO with hundreds of other sites, and you can change neither `innodb_buffer_pool_size`, nor the SQL modes, nor the engine version. On a VPS, you have dedicated resources, you choose between MySQL 8.4 LTS and MariaDB, and you access the `my.cnf` file to tune every parameter. It's the right choice for an e-commerce store, a high-traffic CMS, or an API that needs fast transactions and a generous buffer pool. You also manage your own users, your table-level grants, and your master-replica replication strategy.",{"type":43,"title":44,"items":45},"ul","The concrete benefits of a self-hosted MySQL",[46,47,48,49,50,51],"Dedicated resources: the entire InnoDB buffer pool for your data, with no noisy neighbors.","Full access to `my.cnf`: `innodb_buffer_pool_size`, `max_connections`, strict SQL modes.","Choice of engine: MySQL 8.4 LTS for long-term stability, or MariaDB for its additional engines.","Configurable master-replica replication for offloaded reads or disaster recovery.","Logical (`mysqldump`) or physical (`xtrabackup`) backups depending on your volume.","No arbitrary quota on database size or the number of simultaneous connections.",{"type":39,"title":53,"body":54},"Concrete prerequisites before you start","**Recommended version:** MySQL 8.4 LTS (current release: 8.4.11), the official LTS branch, supported until 2032. Avoid MySQL 8.0 for new installations: active support ends in 2025.\n\n**RAM:** 1 GB minimum for a test environment or lightweight blog. Plan **2 GB** for a WordPress site or small production application, and **4 to 8 GB** for Magento, PrestaShop, or a high-traffic API. The baseline rule: allocate 60 to 70% of available RAM to `innodb_buffer_pool_size`.\n\n**CPU:** 2 vCPU covers most use cases. Scale to 4 vCPU when queries are frequent or when you enable replication.\n\n**Storage:** 30 GB SSD NVMe as a starting point; reserve extra space for binlogs if you enable replication or point-in-time recovery.\n\n**Ports:** MySQL listens on port **3306** by default. This port must **never** be open on the public interface. The `bind-address` parameter in `my.cnf` must point to `127.0.0.1` or the private network address, not `0.0.0.0`.\n\n**System:** Ubuntu 22.04 or 24.04 LTS, Docker and Docker Compose v2, a dedicated persistent volume.",{"type":56,"title":57,"steps":58},"steps","Deploying MySQL 8.4 with Docker in production",[59,62,65,68,71,74],{"title":60,"body":61},"Secure the VPS","Update the system (`apt update && apt upgrade -y`), install Docker, disable password-based SSH login in favor of keys (`PasswordAuthentication no` in `sshd_config`), and close port 3306 to the outside:\n```bash\nufw deny 3306\nufw allow OpenSSH\nufw enable\n```\nMySQL should only listen on the Docker container's internal network.",{"title":63,"body":64},"Define the docker-compose.yml","Declare a `mysql:8.4` service, mount a volume on `\u002Fvar\u002Flib\u002Fmysql`, and pass environment variables through a `.env` file:\n```bash\n# .env\nMYSQL_ROOT_PASSWORD=strong_root_password\nMYSQL_DATABASE=my_database\nMYSQL_USER=app_user\nMYSQL_PASSWORD=app_password\n```\n```bash\n# docker-compose.yml\nservices:\n  mysql:\n    image: mysql:8.4\n    restart: unless-stopped\n    env_file: .env\n    volumes:\n      - mysql_data:\u002Fvar\u002Flib\u002Fmysql\n      - .\u002Fconf.d:\u002Fetc\u002Fmysql\u002Fconf.d:ro\n    ports:\n      - \"127.0.0.1:3306:3306\"\nvolumes:\n  mysql_data:\n```\nNote the `bind` on `127.0.0.1` in `ports`: the port will only be accessible from the VPS itself.",{"title":66,"body":67},"Create a database and a dedicated user","After `docker compose up -d`, connect to the container:\n```bash\ndocker exec -it mysql mysql -u root -p\n```\nThen create the database and the application user:\n```bash\nCREATE DATABASE my_database CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;\nCREATE USER 'app_user'@'%' IDENTIFIED BY 'app_password';\nGRANT SELECT, INSERT, UPDATE, DELETE ON my_database.* TO 'app_user'@'%';\nFLUSH PRIVILEGES;\n```\nLimit grants to the strict minimum: never use `GRANT ALL PRIVILEGES` for an application account.",{"title":69,"body":70},"Harden the installation","Run the equivalent of `mysql_secure_installation` manually:\n```bash\n-- Remove anonymous users\nDELETE FROM mysql.user WHERE User='';\n-- Forbid remote root login\nDELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');\n-- Drop the test database\nDROP DATABASE IF EXISTS test;\nFLUSH PRIVILEGES;\n```\nNever use the `root` account from your application. Verify that `bind-address = 127.0.0.1` is present in your `conf.d\u002Fcustom.cnf`.",{"title":72,"body":73},"Expose over TLS if necessary","If an external application must connect remotely, never expose port 3306 directly on the Internet. Use an **SSH tunnel**:\n```bash\nssh -L 3306:127.0.0.1:3306 user@your-vps.com -N\n```\nThen connect your MySQL client to `127.0.0.1:3306` — traffic travels encrypted over SSH. For a permanent inter-VPS connection, configure MySQL to listen only on the private network interface and allow only the application server's IP in UFW.",{"title":75,"body":76},"Schedule backups","Schedule a daily `mysqldump --single-transaction` via cron for consistent, lock-free dumps:\n```bash\n# \u002Fetc\u002Fcron.d\u002Fmysql-backup\n0 2 * * * root docker exec mysql mysqldump -u root -p\"${MYSQL_ROOT_PASSWORD}\" --single-transaction --all-databases | gzip > \u002Fbackups\u002Fmysql-$(date +\\%Y\\%m\\%d).sql.gz\n```\nKeep at least 7 days of retention and **archive dumps off the VPS**. Test restoration once a month: an untested backup is not a backup. For large databases, switch to Percona XtraBackup for fast physical backups.",{"type":39,"title":78,"body":79},"Security: essential hardening steps","**`mysql_secure_installation` in a container.** The official Docker image does not run it automatically: do it manually after the first startup (step 4 above).\n\n**Remove remote root.** The `root` account must only be accessible from `localhost`. A root account accessible from `%` is the first vulnerability exploited during port scans.\n\n**Dedicated user per application.** Each connected application gets its own MySQL account with rights restricted to its own database. A compromised application cannot then read or modify other applications' data.\n\n**UFW + bind-address.** The double protection is mandatory: `ufw deny 3306` at the system firewall level, and `bind-address = 127.0.0.1` in `my.cnf` on the MySQL side. One does not replace the other.\n\n**Strong passwords.** Use `caching_sha2_password` (default plugin under MySQL 8.x) and generated passwords (at least 20 characters). Store them in managed secrets, never in source code.",{"type":39,"title":81,"body":82},"Optimizing InnoDB performance","These parameters go in your `conf.d\u002Fcustom.cnf` file mounted read-only in the container.\n\n**`innodb_buffer_pool_size`**: the most important parameter. Set it to 60–70% of the VPS RAM. On a 4 GB VPS, that's 2.5 to 2.8 GB. Too small = frequent disk reads; too large = swap.\n\n**`innodb_flush_log_at_trx_commit`**: value `2` offers a good performance\u002Fdurability trade-off for most applications. Value `1` (default) is safer but slower; value `0` is fastest but risky on crash.\n\n**`max_connections`**: adjust to your real load. The default (151) is often too low for a high-traffic application, too high for a small blog. Each connection consumes ~1 MB of RAM.\n\n**`slow_query_log`**: enable it with `slow_query_log = 1` and `long_query_time = 1` to capture all queries exceeding 1 second. It's your primary optimization tool.\n\nSample `custom.cnf` for a 4 GB VPS:\n```bash\n[mysqld]\ninnodb_buffer_pool_size = 2G\ninnodb_flush_log_at_trx_commit = 2\nmax_connections = 200\nslow_query_log = 1\nlong_query_time = 1\nlog_bin = \u002Fvar\u002Flib\u002Fmysql\u002Fbinlog\nbinlog_format = ROW\n```",{"type":39,"title":84,"body":85},"Secure remote access: the SSH tunnel","Exposing port 3306 on your VPS's public interface is a serious security mistake: automated scanners attempt brute-force attacks on this port continuously. The right approach is the SSH tunnel.\n\n**One-time connection from your workstation:**\n```bash\nssh -L 3306:127.0.0.1:3306 user@your-vps.com -N\n```\nThen open your MySQL client on `127.0.0.1:3306` — traffic travels encrypted over SSH.\n\n**Permanent inter-VPS connection:** if your application runs on a second VPS on the same private network, configure MySQL to listen only on the private network address (`bind-address = 10.0.0.X`), and allow only the application server's IP in UFW:\n```bash\nufw allow from 10.0.0.5 to any port 3306\n```\n\n**To avoid:** `bind-address = 0.0.0.0` without a firewall, port 3306 open publicly, or MySQL accounts with `Host='%'` without IP restriction.",{"type":39,"title":87,"body":88},"Automated backups: a complete strategy","A MySQL backup strategy relies on two complementary levels.\n\n**Level 1 — daily logical dump (`mysqldump`):** consistent thanks to `--single-transaction` (no read lock on InnoDB), portable between versions. Suitable for databases up to a few tens of GB.\n\n**Level 2 — binlogs for point-in-time recovery:** enable `log_bin` from the start. Combined with a full weekly dump, binlogs let you replay every transaction up to the second before an incident.\n\n**For large databases — Percona XtraBackup:** physical backup without interrupting the service, fast restoration (file copy rather than SQL re-execution).\n\n**Retention and archiving:** do not store backups only on the VPS. A corrupted VPS takes its own backups with it. Export to object storage or a remote SFTP server, and keep at least 7 days.\n\n**Test restoration:** an untested backup is not a backup. Schedule a monthly test: restore on a temporary container and verify your data is intact.\n\n```bash\n# Restore from a compressed dump\nzcat \u002Fbackups\u002Fmysql-20260917.sql.gz | docker exec -i mysql mysql -u root -p\n```",{"type":39,"title":90,"body":91},"Troubleshooting: 5 common errors","**`Access denied for user 'app'@'...'`**\nThe account exists but the `Host` does not match. MySQL checks the exact source IP. If the application connects from a Docker container, the source IP may be `172.17.0.x` and not `localhost`. Check with:\n```bash\nSELECT User, Host FROM mysql.user;\n```\nSolution: create the account with `'app_user'@'%'` (or the exact container IP) and re-run `FLUSH PRIVILEGES`.\n\n**`Too many connections`**\n`max_connections` has been reached. Check the number of active connections:\n```bash\nSHOW STATUS LIKE 'Threads_connected';\n```\nIncrease `max_connections` in `custom.cnf` **and** verify your application uses a connection pool.\n\n**`Table '...' is marked as crashed`**\nInnoDB corruption, often after an abrupt shutdown. Attempt repair:\n```bash\nmysqlcheck -u root -p --auto-repair --all-databases\n```\nIf the table cannot be repaired, restore from your last consistent backup.\n\n**`Can't connect to MySQL server on '127.0.0.1' (111)`**\nMySQL is not listening on the expected interface. Check `bind-address` in `custom.cnf` and restart the container. Also verify the container is running with `docker compose ps`.\n\n**`ERROR 1819 (HY000): Your password does not satisfy the current policy`**\nMySQL 8's password validation plugin rejects simple passwords. Use a password of at least 8 characters with uppercase letters, digits, and symbols, or set `validate_password.policy = LOW` in `custom.cnf` for development environments only.",{"type":93,"body":94},"tip","Enable binlogs (`log_bin` + `binlog_format=ROW`) from the start: they are the key to point-in-time recovery. Combined with a full weekly `mysqldump`, they let you replay every transaction up to the second before an incident. And if you set up a replica on a second VPS, those same binlogs feed the replica in real time for your offloaded reads.","Host your MySQL on a high-performance VPS","The ServOrbit Cloud VPS with a ready-to-use Docker template gives you SSD storage and resources stated per plan to run MySQL.","Launch my Cloud VPS","\u002Fapplications\u002Fdatabases\u002Fmysql-stack",[100,118,136],{"id":101,"slug":102,"slugs":103,"title":107,"excerpt":108,"readTime":17,"views":109,"isPinned":19,"noindex":19,"publishedAt":110,"updatedAt":111,"category":112,"categories":113,"featuredImage":30,"bgImage":31,"posterImage":115,"relatedSolution":116},56,"postgresql-on-a-vps-a-reliable-and-controlled-database",{"fr":104,"en":102,"ar":105,"es":106},"heberger-postgresql-vps","postgresql-على-خادم-vps-قاعدة-بيانات-موثوقة-ومتحكم-بها","alojar-postgresql-en-un-vps","PostgreSQL on a VPS: reliable, controlled database","Host PostgreSQL on a VPS: production config, pgBackRest backups, streaming HA, RDS\u002FSupabase\u002FNeon comparison and OOM troubleshooting.",1,"2026-04-25T00:00:00+00:00","2026-10-21T15:21:37+00:00",{"id":23,"name":24,"slug":25,"color":26,"icon":27},[114],{"id":23,"name":24,"slug":25,"color":26,"icon":27},"\u002Fblog\u002Fcovers\u002Fheberger-postgresql-vps-poster.svg",{"categorySlug":34,"appSlug":117},"postgresql-stack",{"id":119,"slug":120,"slugs":121,"title":125,"excerpt":126,"readTime":127,"views":18,"isPinned":19,"noindex":19,"publishedAt":128,"updatedAt":129,"category":130,"categories":131,"featuredImage":30,"bgImage":31,"posterImage":133,"relatedSolution":134},58,"hosting-redis-on-a-vps",{"fr":122,"en":120,"ar":123,"es":124},"heberger-redis-vps","استضافة-redis-على-خادم-vps","alojar-redis-en-un-vps","Hosting Redis on a VPS: the complete guide","Deploy Redis on your Cloud VPS: RAM\u002FCPU requirements, security (ACL, TLS), RDB\u002FAOF persistence, troubleshooting, and high availability.",10,"2026-04-23T00:00:00+00:00","2026-09-08T21:59:59+00:00",{"id":23,"name":24,"slug":25,"color":26,"icon":27},[132],{"id":23,"name":24,"slug":25,"color":26,"icon":27},"\u002Fblog\u002Fcovers\u002Fheberger-redis-vps-poster.svg",{"categorySlug":34,"appSlug":135},"redis-stack",{"id":137,"slug":138,"slugs":139,"title":143,"excerpt":144,"readTime":145,"views":18,"isPinned":19,"noindex":19,"publishedAt":146,"updatedAt":147,"category":148,"categories":149,"featuredImage":30,"bgImage":31,"posterImage":151,"relatedSolution":30},59,"hosting-mongodb-on-a-vps",{"fr":140,"en":138,"ar":141,"es":142},"heberger-mongodb-vps","استضافة-mongodb-على-خادم-vps","alojar-mongodb-en-un-vps","Hosting MongoDB on a VPS","Host MongoDB on your Cloud VPS: Docker deployment, replica set, authentication, indexing, and controlled backups.",3,"2026-04-22T00:00:00+00:00","2026-09-08T22:00:02+00:00",{"id":23,"name":24,"slug":25,"color":26,"icon":27},[150],{"id":23,"name":24,"slug":25,"color":26,"icon":27},"\u002Fblog\u002Fcovers\u002Fheberger-mongodb-vps-poster.svg",true,1794270418744]