{"id":1046,"date":"2026-09-03T23:12:14","date_gmt":"2026-09-03T23:12:14","guid":{"rendered":"https:\/\/virtualserversvps.com\/blog\/?p=1046"},"modified":"2026-09-20T22:02:57","modified_gmt":"2026-09-20T22:02:57","slug":"tuned-adm-vps-performance-profiles","status":"publish","type":"post","link":"https:\/\/virtualserversvps.com\/blog\/tuned-adm-vps-performance-profiles\/","title":{"rendered":"tuned-adm on a VPS: Matching Kernel Profiles to Web, Database and Latency-Sensitive Workloads"},"content":{"rendered":"<li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n\n<!-- \/wp:post-content --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list-item -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:list-item --><!-- \/wp:list-item --><!-- \/wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:post-content --><!-- \/wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:post-content --><!-- \/wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:post-content --><!-- \/wp:list-item -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:post-content --><!-- \/wp:list-item -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:post-content --><!-- \/wp:list --><!-- wp:post-content --><!-- wp:paragraph -->\n<p><code>tuned<\/code> is one of the most useful and most misunderstood packages on a Linux VPS. It ships a set of named kernel and device profiles \u2014 curated bundles of CPU governor, scheduler, virtual memory, disk queue and power settings \u2014 and applies them atomically with <code>tuned-adm profile &lt;name&gt;<\/code>. The misunderstanding is treating profiles as magic performance buttons. They are not: a profile is a bundle of conservative tunings that must match your workload or it will actively hurt you. A database VPS running a web-optimised profile will have the wrong read-ahead and dirty-ratio settings; a web VPS running a throughput profile may hold dirty pages too long and spike latency on small writes.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>This article covers the profiles that matter on a VPS, what each one actually changes, how to verify it took effect, and how to build a custom profile that layers your own sysctl values on top. If you have not yet worked through generic kernel tuning, start with our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-kernel-parameters-sysctl-tuning\">VPS kernel parameter and sysctl tuning<\/a>, since a tuned profile is just a managed overlay on the same parameters.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>What tuned actually does<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Installed via <code>apt install tuned<\/code> (Debian\/Ubuntu) or <code>dnf install tuned<\/code> (RHEL family), tuned runs as a daemon with a profile selected at <code>\/etc\/tuned\/active_profile<\/code>. On activation it performs three kinds of changes:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li><strong>sysctl<\/strong> writes \u2014 values like <code>vm.swappiness<\/code>, <code>vm.dirty_ratio<\/code>, <code>vm.dirty_background_ratio<\/code>, <code>net.ipv4.tcp_*<\/code>.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Device settings<\/strong> \u2014 I\/O scheduler on each block device (<code>\/sys\/block\/*\/queue\/scheduler<\/code>), read-ahead (<code>read_ahead_kb<\/code>), and sometimes queue depth.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Plugin actions<\/strong> \u2014 CPU governor and energy\/performance preference via the <code>cpu<\/code> plugin, disk power management, and in some profiles, transparent hugepage behaviour.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Profiles live in <code>\/usr\/lib\/tuned\/&lt;name&gt;\/tuned.conf<\/code>, with local overrides in <code>\/etc\/tuned\/<\/code>. That split matters: your custom profile in <code>\/etc\/tuned\/<\/code> survives package upgrades, edits to <code>\/usr\/lib\/<\/code> do not.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Profile comparison for VPS workloads<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Profile<\/th><th>Intended workload<\/th><th>Key vm settings<\/th><th>I\/O scheduler<\/th><th>Fit for a VPS<\/th><\/tr><\/thead><tbody><tr><td><code>throughput-performance<\/code><\/td><td>Generic server, maximum I\/O and CPU<\/td><td>swappiness=10, dirty_ratio=40, dirty_background=10<\/td><td>mq-deadline \/ none<\/td><td>Good default for web or general servers<\/td><\/tr><tr><td><code>latency-performance<\/code><\/td><td>Latency-sensitive services<\/td><td>swappiness=10, dirty_ratio=10, dirty_background=3<\/td><td>none \/ mq-deadline<\/td><td>Best for databases on SSD\/NVMe<\/td><\/tr><tr><td><code>network-latency<\/code><\/td><td>Network-focused, low per-packet latency<\/td><td>swappiness=10, aggressive THP disabled<\/td><td>unchanged<\/td><td>Useful for proxy\/firewall roles<\/td><\/tr><tr><td><code>network-throughput<\/code><\/td><td>High bandwidth bulk transfer<\/td><td>large TCP buffers<\/td><td>unchanged<\/td><td>Niche; large socket buffers cost RAM<\/td><\/tr><tr><td><code>virtual-guest<\/code><\/td><td>Generic VM guest<\/td><td>moderate swappiness=30<\/td><td>none<\/td><td>Reasonable starting point, not optimal<\/td><\/tr><tr><td><code>balanced<\/code><\/td><td>Default desktop\/server mix<\/td><td>swappiness=60, dirty_ratio=20<\/td><td>mq-deadline<\/td><td>Too conservative for a busy VPS<\/td><\/tr><tr><td><code>powersave<\/code><\/td><td>Minimise energy<\/td><td>swappiness=60+<\/td><td>unchanged<\/td><td>Never on a server<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:paragraph -->\n<p>Two of those rows carry most of the value. <code>throughput-performance<\/code> is the right starting point for a general web VPS. <code>latency-performance<\/code> is the right choice when a database shares the box, because it forces dirty pages to disk far more eagerly (<code>dirty_ratio=10<\/code> instead of 40), which keeps write latency predictable at the cost of a little raw throughput. On a small NVMe VPS with a 256&nbsp;MB buffer pool, that trade is almost always correct.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Applying and verifying a profile<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile throughput-performance<br>tuned-adm active<br>tuned-adm verify<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm verify<\/code> re-reads every setting in the active profile and reports whether the live kernel state matches. If you (or a cloud-init script, or another tuning tool) changed a sysctl after tuned applied, verify will flag the drift. This is the main reason to run tuned rather than a hand-written sysctl file: it detects and can re-assert your configuration.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>To see what a profile will change before you apply it:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile_info latency-performance<br>tuned-adm recommend<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm recommend<\/code> uses the <code>recommend<\/code> plugin to inspect the host (virt type, whether it is a guest, disk type) and suggest a profile. On a KVM VPS it usually suggests <code>virtual-guest<\/code> \u2014 a safe, unambitious choice you will generally want to override.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Building a custom profile that survives upgrades<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>The practical pattern is to inherit the closest stock profile and add your own overrides. Create <code>\/etc\/tuned\/vps-db\/tuned.conf<\/code>:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>[main]<br>summary=Database-optimised profile for 2\u20134 GB VPS<br>include=latency-performance<br><br>[sysctl]<br>vm.dirty_writeback_centisecs=500<br>vm.dirty_expire_centisecs=1000<br>vm.vfs_cache_pressure=50<br>net.core.somaxconn=1024<br><br>[disk]<br>read_ahead_kb=1024<br>scheduler=none<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Then <code>tuned-adm profile vps-db<\/code>. The <code>include=<\/code> line pulls in everything from <code>latency-performance<\/code>, and your sections override or extend it. Values you do not specify are left as the parent set them.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Two cautions. First, do not set <code>vm.swappiness=0<\/code> on a small VPS: with no swap headroom you convert memory pressure directly into OOM kills instead of slow paging. <code>10<\/code> is the practical floor. Second, <code>scheduler=none<\/code> (or <code>mq-deadline<\/code>) is correct for SSDs; on spinning disk you want <code>bfq<\/code> or <code>mq-deadline<\/code> with a larger read-ahead. You can confirm the device is SSD with <code>cat \/sys\/block\/vda\/queue\/rotational<\/code> \u2014 0 means SSD.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Measuring whether it helped<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Changing a profile without measuring is guesswork. A minimal before\/after check on the database VPS:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>sysbench fileio --file-total-size=2G --file-test-mode=rndrw prepare<br>sysbench fileio --file-total-size=2G --file-test-mode=rndrw --time=60 run<br>vmstat 1 60 > \/tmp\/vmstat.after<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Watch <code>si<\/code>\/<code>so<\/code> (swap in\/out) and the <code>bo<\/code> column in <code>vmstat<\/code>. A latency profile should reduce bursty <code>bo<\/code> at the cost of slightly higher steady <code>bo<\/code>. If <code>si<\/code>\/<code>so<\/code> are non-zero in either run, your working set does not fit in RAM and no tunable profile will fix that \u2014 you need a bigger instance or a smaller buffer pool. Our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/diagnosing-vps-disk-io-latency-spikes\">diagnosing VPS disk I\/O latency spikes<\/a> covers how to tell scheduler-caused latency from genuine device saturation.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Practical recommendations<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:post-content --><!-- \/wp:post-content --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list-item -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:list-item --><!-- \/wp:list-item --><!-- wp:post-content --><!-- wp:paragraph -->\n<p><code>tuned<\/code> is one of the most useful and most misunderstood packages on a Linux VPS. It ships a set of named kernel and device profiles \u2014 curated bundles of CPU governor, scheduler, virtual memory, disk queue and power settings \u2014 and applies them atomically with <code>tuned-adm profile &lt;name&gt;<\/code>. The misunderstanding is treating profiles as magic performance buttons. They are not: a profile is a bundle of conservative tunings that must match your workload or it will actively hurt you. A database VPS running a web-optimised profile will have the wrong read-ahead and dirty-ratio settings; a web VPS running a throughput profile may hold dirty pages too long and spike latency on small writes.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>This article covers the profiles that matter on a VPS, what each one actually changes, how to verify it took effect, and how to build a custom profile that layers your own sysctl values on top. If you have not yet worked through generic kernel tuning, start with our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-kernel-parameters-sysctl-tuning\">VPS kernel parameter and sysctl tuning<\/a>, since a tuned profile is just a managed overlay on the same parameters.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>What tuned actually does<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Installed via <code>apt install tuned<\/code> (Debian\/Ubuntu) or <code>dnf install tuned<\/code> (RHEL family), tuned runs as a daemon with a profile selected at <code>\/etc\/tuned\/active_profile<\/code>. On activation it performs three kinds of changes:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li><strong>sysctl<\/strong> writes \u2014 values like <code>vm.swappiness<\/code>, <code>vm.dirty_ratio<\/code>, <code>vm.dirty_background_ratio<\/code>, <code>net.ipv4.tcp_*<\/code>.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Device settings<\/strong> \u2014 I\/O scheduler on each block device (<code>\/sys\/block\/*\/queue\/scheduler<\/code>), read-ahead (<code>read_ahead_kb<\/code>), and sometimes queue depth.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Plugin actions<\/strong> \u2014 CPU governor and energy\/performance preference via the <code>cpu<\/code> plugin, disk power management, and in some profiles, transparent hugepage behaviour.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Profiles live in <code>\/usr\/lib\/tuned\/&lt;name&gt;\/tuned.conf<\/code>, with local overrides in <code>\/etc\/tuned\/<\/code>. That split matters: your custom profile in <code>\/etc\/tuned\/<\/code> survives package upgrades, edits to <code>\/usr\/lib\/<\/code> do not.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Profile comparison for VPS workloads<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Profile<\/th><th>Intended workload<\/th><th>Key vm settings<\/th><th>I\/O scheduler<\/th><th>Fit for a VPS<\/th><\/tr><\/thead><tbody><tr><td><code>throughput-performance<\/code><\/td><td>Generic server, maximum I\/O and CPU<\/td><td>swappiness=10, dirty_ratio=40, dirty_background=10<\/td><td>mq-deadline \/ none<\/td><td>Good default for web or general servers<\/td><\/tr><tr><td><code>latency-performance<\/code><\/td><td>Latency-sensitive services<\/td><td>swappiness=10, dirty_ratio=10, dirty_background=3<\/td><td>none \/ mq-deadline<\/td><td>Best for databases on SSD\/NVMe<\/td><\/tr><tr><td><code>network-latency<\/code><\/td><td>Network-focused, low per-packet latency<\/td><td>swappiness=10, aggressive THP disabled<\/td><td>unchanged<\/td><td>Useful for proxy\/firewall roles<\/td><\/tr><tr><td><code>network-throughput<\/code><\/td><td>High bandwidth bulk transfer<\/td><td>large TCP buffers<\/td><td>unchanged<\/td><td>Niche; large socket buffers cost RAM<\/td><\/tr><tr><td><code>virtual-guest<\/code><\/td><td>Generic VM guest<\/td><td>moderate swappiness=30<\/td><td>none<\/td><td>Reasonable starting point, not optimal<\/td><\/tr><tr><td><code>balanced<\/code><\/td><td>Default desktop\/server mix<\/td><td>swappiness=60, dirty_ratio=20<\/td><td>mq-deadline<\/td><td>Too conservative for a busy VPS<\/td><\/tr><tr><td><code>powersave<\/code><\/td><td>Minimise energy<\/td><td>swappiness=60+<\/td><td>unchanged<\/td><td>Never on a server<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:paragraph -->\n<p>Two of those rows carry most of the value. <code>throughput-performance<\/code> is the right starting point for a general web VPS. <code>latency-performance<\/code> is the right choice when a database shares the box, because it forces dirty pages to disk far more eagerly (<code>dirty_ratio=10<\/code> instead of 40), which keeps write latency predictable at the cost of a little raw throughput. On a small NVMe VPS with a 256&nbsp;MB buffer pool, that trade is almost always correct.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Applying and verifying a profile<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile throughput-performance<br>tuned-adm active<br>tuned-adm verify<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm verify<\/code> re-reads every setting in the active profile and reports whether the live kernel state matches. If you (or a cloud-init script, or another tuning tool) changed a sysctl after tuned applied, verify will flag the drift. This is the main reason to run tuned rather than a hand-written sysctl file: it detects and can re-assert your configuration.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>To see what a profile will change before you apply it:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile_info latency-performance<br>tuned-adm recommend<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm recommend<\/code> uses the <code>recommend<\/code> plugin to inspect the host (virt type, whether it is a guest, disk type) and suggest a profile. On a KVM VPS it usually suggests <code>virtual-guest<\/code> \u2014 a safe, unambitious choice you will generally want to override.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Building a custom profile that survives upgrades<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>The practical pattern is to inherit the closest stock profile and add your own overrides. Create <code>\/etc\/tuned\/vps-db\/tuned.conf<\/code>:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>[main]<br>summary=Database-optimised profile for 2\u20134 GB VPS<br>include=latency-performance<br><br>[sysctl]<br>vm.dirty_writeback_centisecs=500<br>vm.dirty_expire_centisecs=1000<br>vm.vfs_cache_pressure=50<br>net.core.somaxconn=1024<br><br>[disk]<br>read_ahead_kb=1024<br>scheduler=none<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Then <code>tuned-adm profile vps-db<\/code>. The <code>include=<\/code> line pulls in everything from <code>latency-performance<\/code>, and your sections override or extend it. Values you do not specify are left as the parent set them.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Two cautions. First, do not set <code>vm.swappiness=0<\/code> on a small VPS: with no swap headroom you convert memory pressure directly into OOM kills instead of slow paging. <code>10<\/code> is the practical floor. Second, <code>scheduler=none<\/code> (or <code>mq-deadline<\/code>) is correct for SSDs; on spinning disk you want <code>bfq<\/code> or <code>mq-deadline<\/code> with a larger read-ahead. You can confirm the device is SSD with <code>cat \/sys\/block\/vda\/queue\/rotational<\/code> \u2014 0 means SSD.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Measuring whether it helped<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Changing a profile without measuring is guesswork. A minimal before\/after check on the database VPS:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>sysbench fileio --file-total-size=2G --file-test-mode=rndrw prepare<br>sysbench fileio --file-total-size=2G --file-test-mode=rndrw --time=60 run<br>vmstat 1 60 > \/tmp\/vmstat.after<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Watch <code>si<\/code>\/<code>so<\/code> (swap in\/out) and the <code>bo<\/code> column in <code>vmstat<\/code>. A latency profile should reduce bursty <code>bo<\/code> at the cost of slightly higher steady <code>bo<\/code>. If <code>si<\/code>\/<code>so<\/code> are non-zero in either run, your working set does not fit in RAM and no tunable profile will fix that \u2014 you need a bigger instance or a smaller buffer pool. Our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/diagnosing-vps-disk-io-latency-spikes\">diagnosing VPS disk I\/O latency spikes<\/a> covers how to tell scheduler-caused latency from genuine device saturation.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Practical recommendations<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:post-content --><!-- \/wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list --><!-- wp:post-content --><!-- wp:paragraph -->\n<p><code>tuned<\/code> is one of the most useful and most misunderstood packages on a Linux VPS. It ships a set of named kernel and device profiles \u2014 curated bundles of CPU governor, scheduler, virtual memory, disk queue and power settings \u2014 and applies them atomically with <code>tuned-adm profile &lt;name&gt;<\/code>. The misunderstanding is treating profiles as magic performance buttons. They are not: a profile is a bundle of conservative tunings that must match your workload or it will actively hurt you. A database VPS running a web-optimised profile will have the wrong read-ahead and dirty-ratio settings; a web VPS running a throughput profile may hold dirty pages too long and spike latency on small writes.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>This article covers the profiles that matter on a VPS, what each one actually changes, how to verify it took effect, and how to build a custom profile that layers your own sysctl values on top. If you have not yet worked through generic kernel tuning, start with our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-kernel-parameters-sysctl-tuning\">VPS kernel parameter and sysctl tuning<\/a>, since a tuned profile is just a managed overlay on the same parameters.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>What tuned actually does<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Installed via <code>apt install tuned<\/code> (Debian\/Ubuntu) or <code>dnf install tuned<\/code> (RHEL family), tuned runs as a daemon with a profile selected at <code>\/etc\/tuned\/active_profile<\/code>. On activation it performs three kinds of changes:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li><strong>sysctl<\/strong> writes \u2014 values like <code>vm.swappiness<\/code>, <code>vm.dirty_ratio<\/code>, <code>vm.dirty_background_ratio<\/code>, <code>net.ipv4.tcp_*<\/code>.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Device settings<\/strong> \u2014 I\/O scheduler on each block device (<code>\/sys\/block\/*\/queue\/scheduler<\/code>), read-ahead (<code>read_ahead_kb<\/code>), and sometimes queue depth.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Plugin actions<\/strong> \u2014 CPU governor and energy\/performance preference via the <code>cpu<\/code> plugin, disk power management, and in some profiles, transparent hugepage behaviour.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Profiles live in <code>\/usr\/lib\/tuned\/&lt;name&gt;\/tuned.conf<\/code>, with local overrides in <code>\/etc\/tuned\/<\/code>. That split matters: your custom profile in <code>\/etc\/tuned\/<\/code> survives package upgrades, edits to <code>\/usr\/lib\/<\/code> do not.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Profile comparison for VPS workloads<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Profile<\/th><th>Intended workload<\/th><th>Key vm settings<\/th><th>I\/O scheduler<\/th><th>Fit for a VPS<\/th><\/tr><\/thead><tbody><tr><td><code>throughput-performance<\/code><\/td><td>Generic server, maximum I\/O and CPU<\/td><td>swappiness=10, dirty_ratio=40, dirty_background=10<\/td><td>mq-deadline \/ none<\/td><td>Good default for web or general servers<\/td><\/tr><tr><td><code>latency-performance<\/code><\/td><td>Latency-sensitive services<\/td><td>swappiness=10, dirty_ratio=10, dirty_background=3<\/td><td>none \/ mq-deadline<\/td><td>Best for databases on SSD\/NVMe<\/td><\/tr><tr><td><code>network-latency<\/code><\/td><td>Network-focused, low per-packet latency<\/td><td>swappiness=10, aggressive THP disabled<\/td><td>unchanged<\/td><td>Useful for proxy\/firewall roles<\/td><\/tr><tr><td><code>network-throughput<\/code><\/td><td>High bandwidth bulk transfer<\/td><td>large TCP buffers<\/td><td>unchanged<\/td><td>Niche; large socket buffers cost RAM<\/td><\/tr><tr><td><code>virtual-guest<\/code><\/td><td>Generic VM guest<\/td><td>moderate swappiness=30<\/td><td>none<\/td><td>Reasonable starting point, not optimal<\/td><\/tr><tr><td><code>balanced<\/code><\/td><td>Default desktop\/server mix<\/td><td>swappiness=60, dirty_ratio=20<\/td><td>mq-deadline<\/td><td>Too conservative for a busy VPS<\/td><\/tr><tr><td><code>powersave<\/code><\/td><td>Minimise energy<\/td><td>swappiness=60+<\/td><td>unchanged<\/td><td>Never on a server<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:paragraph -->\n<p>Two of those rows carry most of the value. <code>throughput-performance<\/code> is the right starting point for a general web VPS. <code>latency-performance<\/code> is the right choice when a database shares the box, because it forces dirty pages to disk far more eagerly (<code>dirty_ratio=10<\/code> instead of 40), which keeps write latency predictable at the cost of a little raw throughput. On a small NVMe VPS with a 256&nbsp;MB buffer pool, that trade is almost always correct.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Applying and verifying a profile<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile throughput-performance<br>tuned-adm active<br>tuned-adm verify<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm verify<\/code> re-reads every setting in the active profile and reports whether the live kernel state matches. If you (or a cloud-init script, or another tuning tool) changed a sysctl after tuned applied, verify will flag the drift. This is the main reason to run tuned rather than a hand-written sysctl file: it detects and can re-assert your configuration.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>To see what a profile will change before you apply it:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile_info latency-performance<br>tuned-adm recommend<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm recommend<\/code> uses the <code>recommend<\/code> plugin to inspect the host (virt type, whether it is a guest, disk type) and suggest a profile. On a KVM VPS it usually suggests <code>virtual-guest<\/code> \u2014 a safe, unambitious choice you will generally want to override.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Building a custom profile that survives upgrades<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>The practical pattern is to inherit the closest stock profile and add your own overrides. Create <code>\/etc\/tuned\/vps-db\/tuned.conf<\/code>:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>[main]<br>summary=Database-optimised profile for 2\u20134 GB VPS<br>include=latency-performance<br><br>[sysctl]<br>vm.dirty_writeback_centisecs=500<br>vm.dirty_expire_centisecs=1000<br>vm.vfs_cache_pressure=50<br>net.core.somaxconn=1024<br><br>[disk]<br>read_ahead_kb=1024<br>scheduler=none<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Then <code>tuned-adm profile vps-db<\/code>. The <code>include=<\/code> line pulls in everything from <code>latency-performance<\/code>, and your sections override or extend it. Values you do not specify are left as the parent set them.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Two cautions. First, do not set <code>vm.swappiness=0<\/code> on a small VPS: with no swap headroom you convert memory pressure directly into OOM kills instead of slow paging. <code>10<\/code> is the practical floor. Second, <code>scheduler=none<\/code> (or <code>mq-deadline<\/code>) is correct for SSDs; on spinning disk you want <code>bfq<\/code> or <code>mq-deadline<\/code> with a larger read-ahead. You can confirm the device is SSD with <code>cat \/sys\/block\/vda\/queue\/rotational<\/code> \u2014 0 means SSD.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Measuring whether it helped<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Changing a profile without measuring is guesswork. A minimal before\/after check on the database VPS:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>sysbench fileio --file-total-size=2G --file-test-mode=rndrw prepare<br>sysbench fileio --file-total-size=2G --file-test-mode=rndrw --time=60 run<br>vmstat 1 60 > \/tmp\/vmstat.after<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Watch <code>si<\/code>\/<code>so<\/code> (swap in\/out) and the <code>bo<\/code> column in <code>vmstat<\/code>. A latency profile should reduce bursty <code>bo<\/code> at the cost of slightly higher steady <code>bo<\/code>. If <code>si<\/code>\/<code>so<\/code> are non-zero in either run, your working set does not fit in RAM and no tunable profile will fix that \u2014 you need a bigger instance or a smaller buffer pool. Our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/diagnosing-vps-disk-io-latency-spikes\">diagnosing VPS disk I\/O latency spikes<\/a> covers how to tell scheduler-caused latency from genuine device saturation.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Practical recommendations<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:post-content --><!-- \/wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list --><!-- wp:post-content --><!-- wp:paragraph -->\n<p><code>tuned<\/code> is one of the most useful and most misunderstood packages on a Linux VPS. It ships a set of named kernel and device profiles \u2014 curated bundles of CPU governor, scheduler, virtual memory, disk queue and power settings \u2014 and applies them atomically with <code>tuned-adm profile &lt;name&gt;<\/code>. The misunderstanding is treating profiles as magic performance buttons. They are not: a profile is a bundle of conservative tunings that must match your workload or it will actively hurt you. A database VPS running a web-optimised profile will have the wrong read-ahead and dirty-ratio settings; a web VPS running a throughput profile may hold dirty pages too long and spike latency on small writes.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>This article covers the profiles that matter on a VPS, what each one actually changes, how to verify it took effect, and how to build a custom profile that layers your own sysctl values on top. If you have not yet worked through generic kernel tuning, start with our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-kernel-parameters-sysctl-tuning\">VPS kernel parameter and sysctl tuning<\/a>, since a tuned profile is just a managed overlay on the same parameters.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>What tuned actually does<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Installed via <code>apt install tuned<\/code> (Debian\/Ubuntu) or <code>dnf install tuned<\/code> (RHEL family), tuned runs as a daemon with a profile selected at <code>\/etc\/tuned\/active_profile<\/code>. On activation it performs three kinds of changes:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li><strong>sysctl<\/strong> writes \u2014 values like <code>vm.swappiness<\/code>, <code>vm.dirty_ratio<\/code>, <code>vm.dirty_background_ratio<\/code>, <code>net.ipv4.tcp_*<\/code>.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Device settings<\/strong> \u2014 I\/O scheduler on each block device (<code>\/sys\/block\/*\/queue\/scheduler<\/code>), read-ahead (<code>read_ahead_kb<\/code>), and sometimes queue depth.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Plugin actions<\/strong> \u2014 CPU governor and energy\/performance preference via the <code>cpu<\/code> plugin, disk power management, and in some profiles, transparent hugepage behaviour.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Profiles live in <code>\/usr\/lib\/tuned\/&lt;name&gt;\/tuned.conf<\/code>, with local overrides in <code>\/etc\/tuned\/<\/code>. That split matters: your custom profile in <code>\/etc\/tuned\/<\/code> survives package upgrades, edits to <code>\/usr\/lib\/<\/code> do not.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Profile comparison for VPS workloads<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Profile<\/th><th>Intended workload<\/th><th>Key vm settings<\/th><th>I\/O scheduler<\/th><th>Fit for a VPS<\/th><\/tr><\/thead><tbody><tr><td><code>throughput-performance<\/code><\/td><td>Generic server, maximum I\/O and CPU<\/td><td>swappiness=10, dirty_ratio=40, dirty_background=10<\/td><td>mq-deadline \/ none<\/td><td>Good default for web or general servers<\/td><\/tr><tr><td><code>latency-performance<\/code><\/td><td>Latency-sensitive services<\/td><td>swappiness=10, dirty_ratio=10, dirty_background=3<\/td><td>none \/ mq-deadline<\/td><td>Best for databases on SSD\/NVMe<\/td><\/tr><tr><td><code>network-latency<\/code><\/td><td>Network-focused, low per-packet latency<\/td><td>swappiness=10, aggressive THP disabled<\/td><td>unchanged<\/td><td>Useful for proxy\/firewall roles<\/td><\/tr><tr><td><code>network-throughput<\/code><\/td><td>High bandwidth bulk transfer<\/td><td>large TCP buffers<\/td><td>unchanged<\/td><td>Niche; large socket buffers cost RAM<\/td><\/tr><tr><td><code>virtual-guest<\/code><\/td><td>Generic VM guest<\/td><td>moderate swappiness=30<\/td><td>none<\/td><td>Reasonable starting point, not optimal<\/td><\/tr><tr><td><code>balanced<\/code><\/td><td>Default desktop\/server mix<\/td><td>swappiness=60, dirty_ratio=20<\/td><td>mq-deadline<\/td><td>Too conservative for a busy VPS<\/td><\/tr><tr><td><code>powersave<\/code><\/td><td>Minimise energy<\/td><td>swappiness=60+<\/td><td>unchanged<\/td><td>Never on a server<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:paragraph -->\n<p>Two of those rows carry most of the value. <code>throughput-performance<\/code> is the right starting point for a general web VPS. <code>latency-performance<\/code> is the right choice when a database shares the box, because it forces dirty pages to disk far more eagerly (<code>dirty_ratio=10<\/code> instead of 40), which keeps write latency predictable at the cost of a little raw throughput. On a small NVMe VPS with a 256&nbsp;MB buffer pool, that trade is almost always correct.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Applying and verifying a profile<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile throughput-performance<br>tuned-adm active<br>tuned-adm verify<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm verify<\/code> re-reads every setting in the active profile and reports whether the live kernel state matches. If you (or a cloud-init script, or another tuning tool) changed a sysctl after tuned applied, verify will flag the drift. This is the main reason to run tuned rather than a hand-written sysctl file: it detects and can re-assert your configuration.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>To see what a profile will change before you apply it:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile_info latency-performance<br>tuned-adm recommend<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm recommend<\/code> uses the <code>recommend<\/code> plugin to inspect the host (virt type, whether it is a guest, disk type) and suggest a profile. On a KVM VPS it usually suggests <code>virtual-guest<\/code> \u2014 a safe, unambitious choice you will generally want to override.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Building a custom profile that survives upgrades<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>The practical pattern is to inherit the closest stock profile and add your own overrides. Create <code>\/etc\/tuned\/vps-db\/tuned.conf<\/code>:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>[main]<br>summary=Database-optimised profile for 2\u20134 GB VPS<br>include=latency-performance<br><br>[sysctl]<br>vm.dirty_writeback_centisecs=500<br>vm.dirty_expire_centisecs=1000<br>vm.vfs_cache_pressure=50<br>net.core.somaxconn=1024<br><br>[disk]<br>read_ahead_kb=1024<br>scheduler=none<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Then <code>tuned-adm profile vps-db<\/code>. The <code>include=<\/code> line pulls in everything from <code>latency-performance<\/code>, and your sections override or extend it. Values you do not specify are left as the parent set them.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Two cautions. First, do not set <code>vm.swappiness=0<\/code> on a small VPS: with no swap headroom you convert memory pressure directly into OOM kills instead of slow paging. <code>10<\/code> is the practical floor. Second, <code>scheduler=none<\/code> (or <code>mq-deadline<\/code>) is correct for SSDs; on spinning disk you want <code>bfq<\/code> or <code>mq-deadline<\/code> with a larger read-ahead. You can confirm the device is SSD with <code>cat \/sys\/block\/vda\/queue\/rotational<\/code> \u2014 0 means SSD.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Measuring whether it helped<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Changing a profile without measuring is guesswork. A minimal before\/after check on the database VPS:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>sysbench fileio --file-total-size=2G --file-test-mode=rndrw prepare<br>sysbench fileio --file-total-size=2G --file-test-mode=rndrw --time=60 run<br>vmstat 1 60 > \/tmp\/vmstat.after<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Watch <code>si<\/code>\/<code>so<\/code> (swap in\/out) and the <code>bo<\/code> column in <code>vmstat<\/code>. A latency profile should reduce bursty <code>bo<\/code> at the cost of slightly higher steady <code>bo<\/code>. If <code>si<\/code>\/<code>so<\/code> are non-zero in either run, your working set does not fit in RAM and no tunable profile will fix that \u2014 you need a bigger instance or a smaller buffer pool. Our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/diagnosing-vps-disk-io-latency-spikes\">diagnosing VPS disk I\/O latency spikes<\/a> covers how to tell scheduler-caused latency from genuine device saturation.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Practical recommendations<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:post-content --><!-- \/wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list --><!-- wp:post-content --><!-- wp:paragraph -->\n<p><code>tuned<\/code> is one of the most useful and most misunderstood packages on a Linux VPS. It ships a set of named kernel and device profiles \u2014 curated bundles of CPU governor, scheduler, virtual memory, disk queue and power settings \u2014 and applies them atomically with <code>tuned-adm profile &lt;name&gt;<\/code>. The misunderstanding is treating profiles as magic performance buttons. They are not: a profile is a bundle of conservative tunings that must match your workload or it will actively hurt you. A database VPS running a web-optimised profile will have the wrong read-ahead and dirty-ratio settings; a web VPS running a throughput profile may hold dirty pages too long and spike latency on small writes.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>This article covers the profiles that matter on a VPS, what each one actually changes, how to verify it took effect, and how to build a custom profile that layers your own sysctl values on top. If you have not yet worked through generic kernel tuning, start with our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-kernel-parameters-sysctl-tuning\">VPS kernel parameter and sysctl tuning<\/a>, since a tuned profile is just a managed overlay on the same parameters.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>What tuned actually does<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Installed via <code>apt install tuned<\/code> (Debian\/Ubuntu) or <code>dnf install tuned<\/code> (RHEL family), tuned runs as a daemon with a profile selected at <code>\/etc\/tuned\/active_profile<\/code>. On activation it performs three kinds of changes:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li><strong>sysctl<\/strong> writes \u2014 values like <code>vm.swappiness<\/code>, <code>vm.dirty_ratio<\/code>, <code>vm.dirty_background_ratio<\/code>, <code>net.ipv4.tcp_*<\/code>.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Device settings<\/strong> \u2014 I\/O scheduler on each block device (<code>\/sys\/block\/*\/queue\/scheduler<\/code>), read-ahead (<code>read_ahead_kb<\/code>), and sometimes queue depth.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Plugin actions<\/strong> \u2014 CPU governor and energy\/performance preference via the <code>cpu<\/code> plugin, disk power management, and in some profiles, transparent hugepage behaviour.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Profiles live in <code>\/usr\/lib\/tuned\/&lt;name&gt;\/tuned.conf<\/code>, with local overrides in <code>\/etc\/tuned\/<\/code>. That split matters: your custom profile in <code>\/etc\/tuned\/<\/code> survives package upgrades, edits to <code>\/usr\/lib\/<\/code> do not.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Profile comparison for VPS workloads<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Profile<\/th><th>Intended workload<\/th><th>Key vm settings<\/th><th>I\/O scheduler<\/th><th>Fit for a VPS<\/th><\/tr><\/thead><tbody><tr><td><code>throughput-performance<\/code><\/td><td>Generic server, maximum I\/O and CPU<\/td><td>swappiness=10, dirty_ratio=40, dirty_background=10<\/td><td>mq-deadline \/ none<\/td><td>Good default for web or general servers<\/td><\/tr><tr><td><code>latency-performance<\/code><\/td><td>Latency-sensitive services<\/td><td>swappiness=10, dirty_ratio=10, dirty_background=3<\/td><td>none \/ mq-deadline<\/td><td>Best for databases on SSD\/NVMe<\/td><\/tr><tr><td><code>network-latency<\/code><\/td><td>Network-focused, low per-packet latency<\/td><td>swappiness=10, aggressive THP disabled<\/td><td>unchanged<\/td><td>Useful for proxy\/firewall roles<\/td><\/tr><tr><td><code>network-throughput<\/code><\/td><td>High bandwidth bulk transfer<\/td><td>large TCP buffers<\/td><td>unchanged<\/td><td>Niche; large socket buffers cost RAM<\/td><\/tr><tr><td><code>virtual-guest<\/code><\/td><td>Generic VM guest<\/td><td>moderate swappiness=30<\/td><td>none<\/td><td>Reasonable starting point, not optimal<\/td><\/tr><tr><td><code>balanced<\/code><\/td><td>Default desktop\/server mix<\/td><td>swappiness=60, dirty_ratio=20<\/td><td>mq-deadline<\/td><td>Too conservative for a busy VPS<\/td><\/tr><tr><td><code>powersave<\/code><\/td><td>Minimise energy<\/td><td>swappiness=60+<\/td><td>unchanged<\/td><td>Never on a server<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:paragraph -->\n<p>Two of those rows carry most of the value. <code>throughput-performance<\/code> is the right starting point for a general web VPS. <code>latency-performance<\/code> is the right choice when a database shares the box, because it forces dirty pages to disk far more eagerly (<code>dirty_ratio=10<\/code> instead of 40), which keeps write latency predictable at the cost of a little raw throughput. On a small NVMe VPS with a 256&nbsp;MB buffer pool, that trade is almost always correct.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Applying and verifying a profile<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile throughput-performance<br>tuned-adm active<br>tuned-adm verify<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm verify<\/code> re-reads every setting in the active profile and reports whether the live kernel state matches. If you (or a cloud-init script, or another tuning tool) changed a sysctl after tuned applied, verify will flag the drift. This is the main reason to run tuned rather than a hand-written sysctl file: it detects and can re-assert your configuration.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>To see what a profile will change before you apply it:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile_info latency-performance<br>tuned-adm recommend<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm recommend<\/code> uses the <code>recommend<\/code> plugin to inspect the host (virt type, whether it is a guest, disk type) and suggest a profile. On a KVM VPS it usually suggests <code>virtual-guest<\/code> \u2014 a safe, unambitious choice you will generally want to override.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Building a custom profile that survives upgrades<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>The practical pattern is to inherit the closest stock profile and add your own overrides. Create <code>\/etc\/tuned\/vps-db\/tuned.conf<\/code>:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>[main]<br>summary=Database-optimised profile for 2\u20134 GB VPS<br>include=latency-performance<br><br>[sysctl]<br>vm.dirty_writeback_centisecs=500<br>vm.dirty_expire_centisecs=1000<br>vm.vfs_cache_pressure=50<br>net.core.somaxconn=1024<br><br>[disk]<br>read_ahead_kb=1024<br>scheduler=none<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Then <code>tuned-adm profile vps-db<\/code>. The <code>include=<\/code> line pulls in everything from <code>latency-performance<\/code>, and your sections override or extend it. Values you do not specify are left as the parent set them.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Two cautions. First, do not set <code>vm.swappiness=0<\/code> on a small VPS: with no swap headroom you convert memory pressure directly into OOM kills instead of slow paging. <code>10<\/code> is the practical floor. Second, <code>scheduler=none<\/code> (or <code>mq-deadline<\/code>) is correct for SSDs; on spinning disk you want <code>bfq<\/code> or <code>mq-deadline<\/code> with a larger read-ahead. You can confirm the device is SSD with <code>cat \/sys\/block\/vda\/queue\/rotational<\/code> \u2014 0 means SSD.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Measuring whether it helped<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Changing a profile without measuring is guesswork. A minimal before\/after check on the database VPS:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>sysbench fileio --file-total-size=2G --file-test-mode=rndrw prepare<br>sysbench fileio --file-total-size=2G --file-test-mode=rndrw --time=60 run<br>vmstat 1 60 > \/tmp\/vmstat.after<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Watch <code>si<\/code>\/<code>so<\/code> (swap in\/out) and the <code>bo<\/code> column in <code>vmstat<\/code>. A latency profile should reduce bursty <code>bo<\/code> at the cost of slightly higher steady <code>bo<\/code>. If <code>si<\/code>\/<code>so<\/code> are non-zero in either run, your working set does not fit in RAM and no tunable profile will fix that \u2014 you need a bigger instance or a smaller buffer pool. Our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/diagnosing-vps-disk-io-latency-spikes\">diagnosing VPS disk I\/O latency spikes<\/a> covers how to tell scheduler-caused latency from genuine device saturation.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Practical recommendations<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:post-content --><!-- \/wp:list-item -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list --><!-- wp:post-content --><!-- wp:paragraph -->\n<p><code>tuned<\/code> is one of the most useful and most misunderstood packages on a Linux VPS. It ships a set of named kernel and device profiles \u2014 curated bundles of CPU governor, scheduler, virtual memory, disk queue and power settings \u2014 and applies them atomically with <code>tuned-adm profile &lt;name&gt;<\/code>. The misunderstanding is treating profiles as magic performance buttons. They are not: a profile is a bundle of conservative tunings that must match your workload or it will actively hurt you. A database VPS running a web-optimised profile will have the wrong read-ahead and dirty-ratio settings; a web VPS running a throughput profile may hold dirty pages too long and spike latency on small writes.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>This article covers the profiles that matter on a VPS, what each one actually changes, how to verify it took effect, and how to build a custom profile that layers your own sysctl values on top. If you have not yet worked through generic kernel tuning, start with our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-kernel-parameters-sysctl-tuning\">VPS kernel parameter and sysctl tuning<\/a>, since a tuned profile is just a managed overlay on the same parameters.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>What tuned actually does<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Installed via <code>apt install tuned<\/code> (Debian\/Ubuntu) or <code>dnf install tuned<\/code> (RHEL family), tuned runs as a daemon with a profile selected at <code>\/etc\/tuned\/active_profile<\/code>. On activation it performs three kinds of changes:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li><strong>sysctl<\/strong> writes \u2014 values like <code>vm.swappiness<\/code>, <code>vm.dirty_ratio<\/code>, <code>vm.dirty_background_ratio<\/code>, <code>net.ipv4.tcp_*<\/code>.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Device settings<\/strong> \u2014 I\/O scheduler on each block device (<code>\/sys\/block\/*\/queue\/scheduler<\/code>), read-ahead (<code>read_ahead_kb<\/code>), and sometimes queue depth.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Plugin actions<\/strong> \u2014 CPU governor and energy\/performance preference via the <code>cpu<\/code> plugin, disk power management, and in some profiles, transparent hugepage behaviour.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Profiles live in <code>\/usr\/lib\/tuned\/&lt;name&gt;\/tuned.conf<\/code>, with local overrides in <code>\/etc\/tuned\/<\/code>. That split matters: your custom profile in <code>\/etc\/tuned\/<\/code> survives package upgrades, edits to <code>\/usr\/lib\/<\/code> do not.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Profile comparison for VPS workloads<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Profile<\/th><th>Intended workload<\/th><th>Key vm settings<\/th><th>I\/O scheduler<\/th><th>Fit for a VPS<\/th><\/tr><\/thead><tbody><tr><td><code>throughput-performance<\/code><\/td><td>Generic server, maximum I\/O and CPU<\/td><td>swappiness=10, dirty_ratio=40, dirty_background=10<\/td><td>mq-deadline \/ none<\/td><td>Good default for web or general servers<\/td><\/tr><tr><td><code>latency-performance<\/code><\/td><td>Latency-sensitive services<\/td><td>swappiness=10, dirty_ratio=10, dirty_background=3<\/td><td>none \/ mq-deadline<\/td><td>Best for databases on SSD\/NVMe<\/td><\/tr><tr><td><code>network-latency<\/code><\/td><td>Network-focused, low per-packet latency<\/td><td>swappiness=10, aggressive THP disabled<\/td><td>unchanged<\/td><td>Useful for proxy\/firewall roles<\/td><\/tr><tr><td><code>network-throughput<\/code><\/td><td>High bandwidth bulk transfer<\/td><td>large TCP buffers<\/td><td>unchanged<\/td><td>Niche; large socket buffers cost RAM<\/td><\/tr><tr><td><code>virtual-guest<\/code><\/td><td>Generic VM guest<\/td><td>moderate swappiness=30<\/td><td>none<\/td><td>Reasonable starting point, not optimal<\/td><\/tr><tr><td><code>balanced<\/code><\/td><td>Default desktop\/server mix<\/td><td>swappiness=60, dirty_ratio=20<\/td><td>mq-deadline<\/td><td>Too conservative for a busy VPS<\/td><\/tr><tr><td><code>powersave<\/code><\/td><td>Minimise energy<\/td><td>swappiness=60+<\/td><td>unchanged<\/td><td>Never on a server<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:paragraph -->\n<p>Two of those rows carry most of the value. <code>throughput-performance<\/code> is the right starting point for a general web VPS. <code>latency-performance<\/code> is the right choice when a database shares the box, because it forces dirty pages to disk far more eagerly (<code>dirty_ratio=10<\/code> instead of 40), which keeps write latency predictable at the cost of a little raw throughput. On a small NVMe VPS with a 256&nbsp;MB buffer pool, that trade is almost always correct.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Applying and verifying a profile<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile throughput-performance<br>tuned-adm active<br>tuned-adm verify<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm verify<\/code> re-reads every setting in the active profile and reports whether the live kernel state matches. If you (or a cloud-init script, or another tuning tool) changed a sysctl after tuned applied, verify will flag the drift. This is the main reason to run tuned rather than a hand-written sysctl file: it detects and can re-assert your configuration.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>To see what a profile will change before you apply it:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile_info latency-performance<br>tuned-adm recommend<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm recommend<\/code> uses the <code>recommend<\/code> plugin to inspect the host (virt type, whether it is a guest, disk type) and suggest a profile. On a KVM VPS it usually suggests <code>virtual-guest<\/code> \u2014 a safe, unambitious choice you will generally want to override.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Building a custom profile that survives upgrades<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>The practical pattern is to inherit the closest stock profile and add your own overrides. Create <code>\/etc\/tuned\/vps-db\/tuned.conf<\/code>:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>[main]<br>summary=Database-optimised profile for 2\u20134 GB VPS<br>include=latency-performance<br><br>[sysctl]<br>vm.dirty_writeback_centisecs=500<br>vm.dirty_expire_centisecs=1000<br>vm.vfs_cache_pressure=50<br>net.core.somaxconn=1024<br><br>[disk]<br>read_ahead_kb=1024<br>scheduler=none<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Then <code>tuned-adm profile vps-db<\/code>. The <code>include=<\/code> line pulls in everything from <code>latency-performance<\/code>, and your sections override or extend it. Values you do not specify are left as the parent set them.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Two cautions. First, do not set <code>vm.swappiness=0<\/code> on a small VPS: with no swap headroom you convert memory pressure directly into OOM kills instead of slow paging. <code>10<\/code> is the practical floor. Second, <code>scheduler=none<\/code> (or <code>mq-deadline<\/code>) is correct for SSDs; on spinning disk you want <code>bfq<\/code> or <code>mq-deadline<\/code> with a larger read-ahead. You can confirm the device is SSD with <code>cat \/sys\/block\/vda\/queue\/rotational<\/code> \u2014 0 means SSD.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Measuring whether it helped<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Changing a profile without measuring is guesswork. A minimal before\/after check on the database VPS:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>sysbench fileio --file-total-size=2G --file-test-mode=rndrw prepare<br>sysbench fileio --file-total-size=2G --file-test-mode=rndrw --time=60 run<br>vmstat 1 60 > \/tmp\/vmstat.after<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Watch <code>si<\/code>\/<code>so<\/code> (swap in\/out) and the <code>bo<\/code> column in <code>vmstat<\/code>. A latency profile should reduce bursty <code>bo<\/code> at the cost of slightly higher steady <code>bo<\/code>. If <code>si<\/code>\/<code>so<\/code> are non-zero in either run, your working set does not fit in RAM and no tunable profile will fix that \u2014 you need a bigger instance or a smaller buffer pool. Our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/diagnosing-vps-disk-io-latency-spikes\">diagnosing VPS disk I\/O latency spikes<\/a> covers how to tell scheduler-caused latency from genuine device saturation.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Practical recommendations<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:post-content --><!-- \/wp:list-item -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list --><!-- wp:post-content --><!-- wp:paragraph -->\n<p><code>tuned<\/code> is one of the most useful and most misunderstood packages on a Linux VPS. It ships a set of named kernel and device profiles \u2014 curated bundles of CPU governor, scheduler, virtual memory, disk queue and power settings \u2014 and applies them atomically with <code>tuned-adm profile &lt;name&gt;<\/code>. The misunderstanding is treating profiles as magic performance buttons. They are not: a profile is a bundle of conservative tunings that must match your workload or it will actively hurt you. A database VPS running a web-optimised profile will have the wrong read-ahead and dirty-ratio settings; a web VPS running a throughput profile may hold dirty pages too long and spike latency on small writes.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>This article covers the profiles that matter on a VPS, what each one actually changes, how to verify it took effect, and how to build a custom profile that layers your own sysctl values on top. If you have not yet worked through generic kernel tuning, start with our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-kernel-parameters-sysctl-tuning\">VPS kernel parameter and sysctl tuning<\/a>, since a tuned profile is just a managed overlay on the same parameters.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>What tuned actually does<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Installed via <code>apt install tuned<\/code> (Debian\/Ubuntu) or <code>dnf install tuned<\/code> (RHEL family), tuned runs as a daemon with a profile selected at <code>\/etc\/tuned\/active_profile<\/code>. On activation it performs three kinds of changes:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li><strong>sysctl<\/strong> writes \u2014 values like <code>vm.swappiness<\/code>, <code>vm.dirty_ratio<\/code>, <code>vm.dirty_background_ratio<\/code>, <code>net.ipv4.tcp_*<\/code>.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Device settings<\/strong> \u2014 I\/O scheduler on each block device (<code>\/sys\/block\/*\/queue\/scheduler<\/code>), read-ahead (<code>read_ahead_kb<\/code>), and sometimes queue depth.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Plugin actions<\/strong> \u2014 CPU governor and energy\/performance preference via the <code>cpu<\/code> plugin, disk power management, and in some profiles, transparent hugepage behaviour.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Profiles live in <code>\/usr\/lib\/tuned\/&lt;name&gt;\/tuned.conf<\/code>, with local overrides in <code>\/etc\/tuned\/<\/code>. That split matters: your custom profile in <code>\/etc\/tuned\/<\/code> survives package upgrades, edits to <code>\/usr\/lib\/<\/code> do not.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Profile comparison for VPS workloads<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Profile<\/th><th>Intended workload<\/th><th>Key vm settings<\/th><th>I\/O scheduler<\/th><th>Fit for a VPS<\/th><\/tr><\/thead><tbody><tr><td><code>throughput-performance<\/code><\/td><td>Generic server, maximum I\/O and CPU<\/td><td>swappiness=10, dirty_ratio=40, dirty_background=10<\/td><td>mq-deadline \/ none<\/td><td>Good default for web or general servers<\/td><\/tr><tr><td><code>latency-performance<\/code><\/td><td>Latency-sensitive services<\/td><td>swappiness=10, dirty_ratio=10, dirty_background=3<\/td><td>none \/ mq-deadline<\/td><td>Best for databases on SSD\/NVMe<\/td><\/tr><tr><td><code>network-latency<\/code><\/td><td>Network-focused, low per-packet latency<\/td><td>swappiness=10, aggressive THP disabled<\/td><td>unchanged<\/td><td>Useful for proxy\/firewall roles<\/td><\/tr><tr><td><code>network-throughput<\/code><\/td><td>High bandwidth bulk transfer<\/td><td>large TCP buffers<\/td><td>unchanged<\/td><td>Niche; large socket buffers cost RAM<\/td><\/tr><tr><td><code>virtual-guest<\/code><\/td><td>Generic VM guest<\/td><td>moderate swappiness=30<\/td><td>none<\/td><td>Reasonable starting point, not optimal<\/td><\/tr><tr><td><code>balanced<\/code><\/td><td>Default desktop\/server mix<\/td><td>swappiness=60, dirty_ratio=20<\/td><td>mq-deadline<\/td><td>Too conservative for a busy VPS<\/td><\/tr><tr><td><code>powersave<\/code><\/td><td>Minimise energy<\/td><td>swappiness=60+<\/td><td>unchanged<\/td><td>Never on a server<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:paragraph -->\n<p>Two of those rows carry most of the value. <code>throughput-performance<\/code> is the right starting point for a general web VPS. <code>latency-performance<\/code> is the right choice when a database shares the box, because it forces dirty pages to disk far more eagerly (<code>dirty_ratio=10<\/code> instead of 40), which keeps write latency predictable at the cost of a little raw throughput. On a small NVMe VPS with a 256&nbsp;MB buffer pool, that trade is almost always correct.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Applying and verifying a profile<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile throughput-performance<br>tuned-adm active<br>tuned-adm verify<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm verify<\/code> re-reads every setting in the active profile and reports whether the live kernel state matches. If you (or a cloud-init script, or another tuning tool) changed a sysctl after tuned applied, verify will flag the drift. This is the main reason to run tuned rather than a hand-written sysctl file: it detects and can re-assert your configuration.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>To see what a profile will change before you apply it:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile_info latency-performance<br>tuned-adm recommend<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm recommend<\/code> uses the <code>recommend<\/code> plugin to inspect the host (virt type, whether it is a guest, disk type) and suggest a profile. On a KVM VPS it usually suggests <code>virtual-guest<\/code> \u2014 a safe, unambitious choice you will generally want to override.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Building a custom profile that survives upgrades<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>The practical pattern is to inherit the closest stock profile and add your own overrides. Create <code>\/etc\/tuned\/vps-db\/tuned.conf<\/code>:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>[main]<br>summary=Database-optimised profile for 2\u20134 GB VPS<br>include=latency-performance<br><br>[sysctl]<br>vm.dirty_writeback_centisecs=500<br>vm.dirty_expire_centisecs=1000<br>vm.vfs_cache_pressure=50<br>net.core.somaxconn=1024<br><br>[disk]<br>read_ahead_kb=1024<br>scheduler=none<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Then <code>tuned-adm profile vps-db<\/code>. The <code>include=<\/code> line pulls in everything from <code>latency-performance<\/code>, and your sections override or extend it. Values you do not specify are left as the parent set them.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Two cautions. First, do not set <code>vm.swappiness=0<\/code> on a small VPS: with no swap headroom you convert memory pressure directly into OOM kills instead of slow paging. <code>10<\/code> is the practical floor. Second, <code>scheduler=none<\/code> (or <code>mq-deadline<\/code>) is correct for SSDs; on spinning disk you want <code>bfq<\/code> or <code>mq-deadline<\/code> with a larger read-ahead. You can confirm the device is SSD with <code>cat \/sys\/block\/vda\/queue\/rotational<\/code> \u2014 0 means SSD.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Measuring whether it helped<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Changing a profile without measuring is guesswork. A minimal before\/after check on the database VPS:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>sysbench fileio --file-total-size=2G --file-test-mode=rndrw prepare<br>sysbench fileio --file-total-size=2G --file-test-mode=rndrw --time=60 run<br>vmstat 1 60 > \/tmp\/vmstat.after<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Watch <code>si<\/code>\/<code>so<\/code> (swap in\/out) and the <code>bo<\/code> column in <code>vmstat<\/code>. A latency profile should reduce bursty <code>bo<\/code> at the cost of slightly higher steady <code>bo<\/code>. If <code>si<\/code>\/<code>so<\/code> are non-zero in either run, your working set does not fit in RAM and no tunable profile will fix that \u2014 you need a bigger instance or a smaller buffer pool. Our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/diagnosing-vps-disk-io-latency-spikes\">diagnosing VPS disk I\/O latency spikes<\/a> covers how to tell scheduler-caused latency from genuine device saturation.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Practical recommendations<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:post-content --><!-- \/wp:list --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list-item -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:list-item --><!-- \/wp:list-item -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list --><!-- wp:post-content --><!-- wp:paragraph -->\n<p><code>tuned<\/code> is one of the most useful and most misunderstood packages on a Linux VPS. It ships a set of named kernel and device profiles \u2014 curated bundles of CPU governor, scheduler, virtual memory, disk queue and power settings \u2014 and applies them atomically with <code>tuned-adm profile &lt;name&gt;<\/code>. The misunderstanding is treating profiles as magic performance buttons. They are not: a profile is a bundle of conservative tunings that must match your workload or it will actively hurt you. A database VPS running a web-optimised profile will have the wrong read-ahead and dirty-ratio settings; a web VPS running a throughput profile may hold dirty pages too long and spike latency on small writes.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>This article covers the profiles that matter on a VPS, what each one actually changes, how to verify it took effect, and how to build a custom profile that layers your own sysctl values on top. If you have not yet worked through generic kernel tuning, start with our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-kernel-parameters-sysctl-tuning\">VPS kernel parameter and sysctl tuning<\/a>, since a tuned profile is just a managed overlay on the same parameters.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>What tuned actually does<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Installed via <code>apt install tuned<\/code> (Debian\/Ubuntu) or <code>dnf install tuned<\/code> (RHEL family), tuned runs as a daemon with a profile selected at <code>\/etc\/tuned\/active_profile<\/code>. On activation it performs three kinds of changes:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li><strong>sysctl<\/strong> writes \u2014 values like <code>vm.swappiness<\/code>, <code>vm.dirty_ratio<\/code>, <code>vm.dirty_background_ratio<\/code>, <code>net.ipv4.tcp_*<\/code>.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Device settings<\/strong> \u2014 I\/O scheduler on each block device (<code>\/sys\/block\/*\/queue\/scheduler<\/code>), read-ahead (<code>read_ahead_kb<\/code>), and sometimes queue depth.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Plugin actions<\/strong> \u2014 CPU governor and energy\/performance preference via the <code>cpu<\/code> plugin, disk power management, and in some profiles, transparent hugepage behaviour.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Profiles live in <code>\/usr\/lib\/tuned\/&lt;name&gt;\/tuned.conf<\/code>, with local overrides in <code>\/etc\/tuned\/<\/code>. That split matters: your custom profile in <code>\/etc\/tuned\/<\/code> survives package upgrades, edits to <code>\/usr\/lib\/<\/code> do not.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Profile comparison for VPS workloads<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Profile<\/th><th>Intended workload<\/th><th>Key vm settings<\/th><th>I\/O scheduler<\/th><th>Fit for a VPS<\/th><\/tr><\/thead><tbody><tr><td><code>throughput-performance<\/code><\/td><td>Generic server, maximum I\/O and CPU<\/td><td>swappiness=10, dirty_ratio=40, dirty_background=10<\/td><td>mq-deadline \/ none<\/td><td>Good default for web or general servers<\/td><\/tr><tr><td><code>latency-performance<\/code><\/td><td>Latency-sensitive services<\/td><td>swappiness=10, dirty_ratio=10, dirty_background=3<\/td><td>none \/ mq-deadline<\/td><td>Best for databases on SSD\/NVMe<\/td><\/tr><tr><td><code>network-latency<\/code><\/td><td>Network-focused, low per-packet latency<\/td><td>swappiness=10, aggressive THP disabled<\/td><td>unchanged<\/td><td>Useful for proxy\/firewall roles<\/td><\/tr><tr><td><code>network-throughput<\/code><\/td><td>High bandwidth bulk transfer<\/td><td>large TCP buffers<\/td><td>unchanged<\/td><td>Niche; large socket buffers cost RAM<\/td><\/tr><tr><td><code>virtual-guest<\/code><\/td><td>Generic VM guest<\/td><td>moderate swappiness=30<\/td><td>none<\/td><td>Reasonable starting point, not optimal<\/td><\/tr><tr><td><code>balanced<\/code><\/td><td>Default desktop\/server mix<\/td><td>swappiness=60, dirty_ratio=20<\/td><td>mq-deadline<\/td><td>Too conservative for a busy VPS<\/td><\/tr><tr><td><code>powersave<\/code><\/td><td>Minimise energy<\/td><td>swappiness=60+<\/td><td>unchanged<\/td><td>Never on a server<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:paragraph -->\n<p>Two of those rows carry most of the value. <code>throughput-performance<\/code> is the right starting point for a general web VPS. <code>latency-performance<\/code> is the right choice when a database shares the box, because it forces dirty pages to disk far more eagerly (<code>dirty_ratio=10<\/code> instead of 40), which keeps write latency predictable at the cost of a little raw throughput. On a small NVMe VPS with a 256&nbsp;MB buffer pool, that trade is almost always correct.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Applying and verifying a profile<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile throughput-performance<br>tuned-adm active<br>tuned-adm verify<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm verify<\/code> re-reads every setting in the active profile and reports whether the live kernel state matches. If you (or a cloud-init script, or another tuning tool) changed a sysctl after tuned applied, verify will flag the drift. This is the main reason to run tuned rather than a hand-written sysctl file: it detects and can re-assert your configuration.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>To see what a profile will change before you apply it:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile_info latency-performance<br>tuned-adm recommend<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm recommend<\/code> uses the <code>recommend<\/code> plugin to inspect the host (virt type, whether it is a guest, disk type) and suggest a profile. On a KVM VPS it usually suggests <code>virtual-guest<\/code> \u2014 a safe, unambitious choice you will generally want to override.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Building a custom profile that survives upgrades<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>The practical pattern is to inherit the closest stock profile and add your own overrides. Create <code>\/etc\/tuned\/vps-db\/tuned.conf<\/code>:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>[main]<br>summary=Database-optimised profile for 2\u20134 GB VPS<br>include=latency-performance<br><br>[sysctl]<br>vm.dirty_writeback_centisecs=500<br>vm.dirty_expire_centisecs=1000<br>vm.vfs_cache_pressure=50<br>net.core.somaxconn=1024<br><br>[disk]<br>read_ahead_kb=1024<br>scheduler=none<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Then <code>tuned-adm profile vps-db<\/code>. The <code>include=<\/code> line pulls in everything from <code>latency-performance<\/code>, and your sections override or extend it. Values you do not specify are left as the parent set them.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Two cautions. First, do not set <code>vm.swappiness=0<\/code> on a small VPS: with no swap headroom you convert memory pressure directly into OOM kills instead of slow paging. <code>10<\/code> is the practical floor. Second, <code>scheduler=none<\/code> (or <code>mq-deadline<\/code>) is correct for SSDs; on spinning disk you want <code>bfq<\/code> or <code>mq-deadline<\/code> with a larger read-ahead. You can confirm the device is SSD with <code>cat \/sys\/block\/vda\/queue\/rotational<\/code> \u2014 0 means SSD.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Measuring whether it helped<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Changing a profile without measuring is guesswork. A minimal before\/after check on the database VPS:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>sysbench fileio --file-total-size=2G --file-test-mode=rndrw prepare<br>sysbench fileio --file-total-size=2G --file-test-mode=rndrw --time=60 run<br>vmstat 1 60 > \/tmp\/vmstat.after<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Watch <code>si<\/code>\/<code>so<\/code> (swap in\/out) and the <code>bo<\/code> column in <code>vmstat<\/code>. A latency profile should reduce bursty <code>bo<\/code> at the cost of slightly higher steady <code>bo<\/code>. If <code>si<\/code>\/<code>so<\/code> are non-zero in either run, your working set does not fit in RAM and no tunable profile will fix that \u2014 you need a bigger instance or a smaller buffer pool. Our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/diagnosing-vps-disk-io-latency-spikes\">diagnosing VPS disk I\/O latency spikes<\/a> covers how to tell scheduler-caused latency from genuine device saturation.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Practical recommendations<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:post-content --><!-- \/wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list-item -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list --><!-- wp:post-content --><!-- wp:paragraph -->\n<p><code>tuned<\/code> is one of the most useful and most misunderstood packages on a Linux VPS. It ships a set of named kernel and device profiles \u2014 curated bundles of CPU governor, scheduler, virtual memory, disk queue and power settings \u2014 and applies them atomically with <code>tuned-adm profile &lt;name&gt;<\/code>. The misunderstanding is treating profiles as magic performance buttons. They are not: a profile is a bundle of conservative tunings that must match your workload or it will actively hurt you. A database VPS running a web-optimised profile will have the wrong read-ahead and dirty-ratio settings; a web VPS running a throughput profile may hold dirty pages too long and spike latency on small writes.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>This article covers the profiles that matter on a VPS, what each one actually changes, how to verify it took effect, and how to build a custom profile that layers your own sysctl values on top. If you have not yet worked through generic kernel tuning, start with our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-kernel-parameters-sysctl-tuning\">VPS kernel parameter and sysctl tuning<\/a>, since a tuned profile is just a managed overlay on the same parameters.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>What tuned actually does<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Installed via <code>apt install tuned<\/code> (Debian\/Ubuntu) or <code>dnf install tuned<\/code> (RHEL family), tuned runs as a daemon with a profile selected at <code>\/etc\/tuned\/active_profile<\/code>. On activation it performs three kinds of changes:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li><strong>sysctl<\/strong> writes \u2014 values like <code>vm.swappiness<\/code>, <code>vm.dirty_ratio<\/code>, <code>vm.dirty_background_ratio<\/code>, <code>net.ipv4.tcp_*<\/code>.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Device settings<\/strong> \u2014 I\/O scheduler on each block device (<code>\/sys\/block\/*\/queue\/scheduler<\/code>), read-ahead (<code>read_ahead_kb<\/code>), and sometimes queue depth.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Plugin actions<\/strong> \u2014 CPU governor and energy\/performance preference via the <code>cpu<\/code> plugin, disk power management, and in some profiles, transparent hugepage behaviour.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Profiles live in <code>\/usr\/lib\/tuned\/&lt;name&gt;\/tuned.conf<\/code>, with local overrides in <code>\/etc\/tuned\/<\/code>. That split matters: your custom profile in <code>\/etc\/tuned\/<\/code> survives package upgrades, edits to <code>\/usr\/lib\/<\/code> do not.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Profile comparison for VPS workloads<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Profile<\/th><th>Intended workload<\/th><th>Key vm settings<\/th><th>I\/O scheduler<\/th><th>Fit for a VPS<\/th><\/tr><\/thead><tbody><tr><td><code>throughput-performance<\/code><\/td><td>Generic server, maximum I\/O and CPU<\/td><td>swappiness=10, dirty_ratio=40, dirty_background=10<\/td><td>mq-deadline \/ none<\/td><td>Good default for web or general servers<\/td><\/tr><tr><td><code>latency-performance<\/code><\/td><td>Latency-sensitive services<\/td><td>swappiness=10, dirty_ratio=10, dirty_background=3<\/td><td>none \/ mq-deadline<\/td><td>Best for databases on SSD\/NVMe<\/td><\/tr><tr><td><code>network-latency<\/code><\/td><td>Network-focused, low per-packet latency<\/td><td>swappiness=10, aggressive THP disabled<\/td><td>unchanged<\/td><td>Useful for proxy\/firewall roles<\/td><\/tr><tr><td><code>network-throughput<\/code><\/td><td>High bandwidth bulk transfer<\/td><td>large TCP buffers<\/td><td>unchanged<\/td><td>Niche; large socket buffers cost RAM<\/td><\/tr><tr><td><code>virtual-guest<\/code><\/td><td>Generic VM guest<\/td><td>moderate swappiness=30<\/td><td>none<\/td><td>Reasonable starting point, not optimal<\/td><\/tr><tr><td><code>balanced<\/code><\/td><td>Default desktop\/server mix<\/td><td>swappiness=60, dirty_ratio=20<\/td><td>mq-deadline<\/td><td>Too conservative for a busy VPS<\/td><\/tr><tr><td><code>powersave<\/code><\/td><td>Minimise energy<\/td><td>swappiness=60+<\/td><td>unchanged<\/td><td>Never on a server<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:paragraph -->\n<p>Two of those rows carry most of the value. <code>throughput-performance<\/code> is the right starting point for a general web VPS. <code>latency-performance<\/code> is the right choice when a database shares the box, because it forces dirty pages to disk far more eagerly (<code>dirty_ratio=10<\/code> instead of 40), which keeps write latency predictable at the cost of a little raw throughput. On a small NVMe VPS with a 256&nbsp;MB buffer pool, that trade is almost always correct.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Applying and verifying a profile<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile throughput-performance<br>tuned-adm active<br>tuned-adm verify<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm verify<\/code> re-reads every setting in the active profile and reports whether the live kernel state matches. If you (or a cloud-init script, or another tuning tool) changed a sysctl after tuned applied, verify will flag the drift. This is the main reason to run tuned rather than a hand-written sysctl file: it detects and can re-assert your configuration.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>To see what a profile will change before you apply it:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile_info latency-performance<br>tuned-adm recommend<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm recommend<\/code> uses the <code>recommend<\/code> plugin to inspect the host (virt type, whether it is a guest, disk type) and suggest a profile. On a KVM VPS it usually suggests <code>virtual-guest<\/code> \u2014 a safe, unambitious choice you will generally want to override.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Building a custom profile that survives upgrades<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>The practical pattern is to inherit the closest stock profile and add your own overrides. Create <code>\/etc\/tuned\/vps-db\/tuned.conf<\/code>:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>[main]<br>summary=Database-optimised profile for 2\u20134 GB VPS<br>include=latency-performance<br><br>[sysctl]<br>vm.dirty_writeback_centisecs=500<br>vm.dirty_expire_centisecs=1000<br>vm.vfs_cache_pressure=50<br>net.core.somaxconn=1024<br><br>[disk]<br>read_ahead_kb=1024<br>scheduler=none<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Then <code>tuned-adm profile vps-db<\/code>. The <code>include=<\/code> line pulls in everything from <code>latency-performance<\/code>, and your sections override or extend it. Values you do not specify are left as the parent set them.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Two cautions. First, do not set <code>vm.swappiness=0<\/code> on a small VPS: with no swap headroom you convert memory pressure directly into OOM kills instead of slow paging. <code>10<\/code> is the practical floor. Second, <code>scheduler=none<\/code> (or <code>mq-deadline<\/code>) is correct for SSDs; on spinning disk you want <code>bfq<\/code> or <code>mq-deadline<\/code> with a larger read-ahead. You can confirm the device is SSD with <code>cat \/sys\/block\/vda\/queue\/rotational<\/code> \u2014 0 means SSD.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Measuring whether it helped<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Changing a profile without measuring is guesswork. A minimal before\/after check on the database VPS:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>sysbench fileio --file-total-size=2G --file-test-mode=rndrw prepare<br>sysbench fileio --file-total-size=2G --file-test-mode=rndrw --time=60 run<br>vmstat 1 60 > \/tmp\/vmstat.after<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Watch <code>si<\/code>\/<code>so<\/code> (swap in\/out) and the <code>bo<\/code> column in <code>vmstat<\/code>. A latency profile should reduce bursty <code>bo<\/code> at the cost of slightly higher steady <code>bo<\/code>. If <code>si<\/code>\/<code>so<\/code> are non-zero in either run, your working set does not fit in RAM and no tunable profile will fix that \u2014 you need a bigger instance or a smaller buffer pool. Our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/diagnosing-vps-disk-io-latency-spikes\">diagnosing VPS disk I\/O latency spikes<\/a> covers how to tell scheduler-caused latency from genuine device saturation.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Practical recommendations<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:post-content --><!-- \/wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list-item -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list --><!-- wp:post-content --><!-- wp:paragraph -->\n<p><code>tuned<\/code> is one of the most useful and most misunderstood packages on a Linux VPS. It ships a set of named kernel and device profiles \u2014 curated bundles of CPU governor, scheduler, virtual memory, disk queue and power settings \u2014 and applies them atomically with <code>tuned-adm profile &lt;name&gt;<\/code>. The misunderstanding is treating profiles as magic performance buttons. They are not: a profile is a bundle of conservative tunings that must match your workload or it will actively hurt you. A database VPS running a web-optimised profile will have the wrong read-ahead and dirty-ratio settings; a web VPS running a throughput profile may hold dirty pages too long and spike latency on small writes.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>This article covers the profiles that matter on a VPS, what each one actually changes, how to verify it took effect, and how to build a custom profile that layers your own sysctl values on top. If you have not yet worked through generic kernel tuning, start with our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-kernel-parameters-sysctl-tuning\">VPS kernel parameter and sysctl tuning<\/a>, since a tuned profile is just a managed overlay on the same parameters.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>What tuned actually does<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Installed via <code>apt install tuned<\/code> (Debian\/Ubuntu) or <code>dnf install tuned<\/code> (RHEL family), tuned runs as a daemon with a profile selected at <code>\/etc\/tuned\/active_profile<\/code>. On activation it performs three kinds of changes:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li><strong>sysctl<\/strong> writes \u2014 values like <code>vm.swappiness<\/code>, <code>vm.dirty_ratio<\/code>, <code>vm.dirty_background_ratio<\/code>, <code>net.ipv4.tcp_*<\/code>.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Device settings<\/strong> \u2014 I\/O scheduler on each block device (<code>\/sys\/block\/*\/queue\/scheduler<\/code>), read-ahead (<code>read_ahead_kb<\/code>), and sometimes queue depth.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Plugin actions<\/strong> \u2014 CPU governor and energy\/performance preference via the <code>cpu<\/code> plugin, disk power management, and in some profiles, transparent hugepage behaviour.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Profiles live in <code>\/usr\/lib\/tuned\/&lt;name&gt;\/tuned.conf<\/code>, with local overrides in <code>\/etc\/tuned\/<\/code>. That split matters: your custom profile in <code>\/etc\/tuned\/<\/code> survives package upgrades, edits to <code>\/usr\/lib\/<\/code> do not.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Profile comparison for VPS workloads<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Profile<\/th><th>Intended workload<\/th><th>Key vm settings<\/th><th>I\/O scheduler<\/th><th>Fit for a VPS<\/th><\/tr><\/thead><tbody><tr><td><code>throughput-performance<\/code><\/td><td>Generic server, maximum I\/O and CPU<\/td><td>swappiness=10, dirty_ratio=40, dirty_background=10<\/td><td>mq-deadline \/ none<\/td><td>Good default for web or general servers<\/td><\/tr><tr><td><code>latency-performance<\/code><\/td><td>Latency-sensitive services<\/td><td>swappiness=10, dirty_ratio=10, dirty_background=3<\/td><td>none \/ mq-deadline<\/td><td>Best for databases on SSD\/NVMe<\/td><\/tr><tr><td><code>network-latency<\/code><\/td><td>Network-focused, low per-packet latency<\/td><td>swappiness=10, aggressive THP disabled<\/td><td>unchanged<\/td><td>Useful for proxy\/firewall roles<\/td><\/tr><tr><td><code>network-throughput<\/code><\/td><td>High bandwidth bulk transfer<\/td><td>large TCP buffers<\/td><td>unchanged<\/td><td>Niche; large socket buffers cost RAM<\/td><\/tr><tr><td><code>virtual-guest<\/code><\/td><td>Generic VM guest<\/td><td>moderate swappiness=30<\/td><td>none<\/td><td>Reasonable starting point, not optimal<\/td><\/tr><tr><td><code>balanced<\/code><\/td><td>Default desktop\/server mix<\/td><td>swappiness=60, dirty_ratio=20<\/td><td>mq-deadline<\/td><td>Too conservative for a busy VPS<\/td><\/tr><tr><td><code>powersave<\/code><\/td><td>Minimise energy<\/td><td>swappiness=60+<\/td><td>unchanged<\/td><td>Never on a server<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:paragraph -->\n<p>Two of those rows carry most of the value. <code>throughput-performance<\/code> is the right starting point for a general web VPS. <code>latency-performance<\/code> is the right choice when a database shares the box, because it forces dirty pages to disk far more eagerly (<code>dirty_ratio=10<\/code> instead of 40), which keeps write latency predictable at the cost of a little raw throughput. On a small NVMe VPS with a 256&nbsp;MB buffer pool, that trade is almost always correct.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Applying and verifying a profile<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile throughput-performance<br>tuned-adm active<br>tuned-adm verify<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm verify<\/code> re-reads every setting in the active profile and reports whether the live kernel state matches. If you (or a cloud-init script, or another tuning tool) changed a sysctl after tuned applied, verify will flag the drift. This is the main reason to run tuned rather than a hand-written sysctl file: it detects and can re-assert your configuration.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>To see what a profile will change before you apply it:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile_info latency-performance<br>tuned-adm recommend<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm recommend<\/code> uses the <code>recommend<\/code> plugin to inspect the host (virt type, whether it is a guest, disk type) and suggest a profile. On a KVM VPS it usually suggests <code>virtual-guest<\/code> \u2014 a safe, unambitious choice you will generally want to override.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Building a custom profile that survives upgrades<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>The practical pattern is to inherit the closest stock profile and add your own overrides. Create <code>\/etc\/tuned\/vps-db\/tuned.conf<\/code>:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>[main]<br>summary=Database-optimised profile for 2\u20134 GB VPS<br>include=latency-performance<br><br>[sysctl]<br>vm.dirty_writeback_centisecs=500<br>vm.dirty_expire_centisecs=1000<br>vm.vfs_cache_pressure=50<br>net.core.somaxconn=1024<br><br>[disk]<br>read_ahead_kb=1024<br>scheduler=none<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Then <code>tuned-adm profile vps-db<\/code>. The <code>include=<\/code> line pulls in everything from <code>latency-performance<\/code>, and your sections override or extend it. Values you do not specify are left as the parent set them.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Two cautions. First, do not set <code>vm.swappiness=0<\/code> on a small VPS: with no swap headroom you convert memory pressure directly into OOM kills instead of slow paging. <code>10<\/code> is the practical floor. Second, <code>scheduler=none<\/code> (or <code>mq-deadline<\/code>) is correct for SSDs; on spinning disk you want <code>bfq<\/code> or <code>mq-deadline<\/code> with a larger read-ahead. You can confirm the device is SSD with <code>cat \/sys\/block\/vda\/queue\/rotational<\/code> \u2014 0 means SSD.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Measuring whether it helped<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Changing a profile without measuring is guesswork. A minimal before\/after check on the database VPS:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>sysbench fileio --file-total-size=2G --file-test-mode=rndrw prepare<br>sysbench fileio --file-total-size=2G --file-test-mode=rndrw --time=60 run<br>vmstat 1 60 > \/tmp\/vmstat.after<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Watch <code>si<\/code>\/<code>so<\/code> (swap in\/out) and the <code>bo<\/code> column in <code>vmstat<\/code>. A latency profile should reduce bursty <code>bo<\/code> at the cost of slightly higher steady <code>bo<\/code>. If <code>si<\/code>\/<code>so<\/code> are non-zero in either run, your working set does not fit in RAM and no tunable profile will fix that \u2014 you need a bigger instance or a smaller buffer pool. Our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/diagnosing-vps-disk-io-latency-spikes\">diagnosing VPS disk I\/O latency spikes<\/a> covers how to tell scheduler-caused latency from genuine device saturation.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Practical recommendations<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:post-content --><!-- \/wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list-item -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list --><!-- wp:post-content --><!-- wp:paragraph -->\n<p><code>tuned<\/code> is one of the most useful and most misunderstood packages on a Linux VPS. It ships a set of named kernel and device profiles \u2014 curated bundles of CPU governor, scheduler, virtual memory, disk queue and power settings \u2014 and applies them atomically with <code>tuned-adm profile &lt;name&gt;<\/code>. The misunderstanding is treating profiles as magic performance buttons. They are not: a profile is a bundle of conservative tunings that must match your workload or it will actively hurt you. A database VPS running a web-optimised profile will have the wrong read-ahead and dirty-ratio settings; a web VPS running a throughput profile may hold dirty pages too long and spike latency on small writes.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>This article covers the profiles that matter on a VPS, what each one actually changes, how to verify it took effect, and how to build a custom profile that layers your own sysctl values on top. If you have not yet worked through generic kernel tuning, start with our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-kernel-parameters-sysctl-tuning\">VPS kernel parameter and sysctl tuning<\/a>, since a tuned profile is just a managed overlay on the same parameters.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>What tuned actually does<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Installed via <code>apt install tuned<\/code> (Debian\/Ubuntu) or <code>dnf install tuned<\/code> (RHEL family), tuned runs as a daemon with a profile selected at <code>\/etc\/tuned\/active_profile<\/code>. On activation it performs three kinds of changes:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li><strong>sysctl<\/strong> writes \u2014 values like <code>vm.swappiness<\/code>, <code>vm.dirty_ratio<\/code>, <code>vm.dirty_background_ratio<\/code>, <code>net.ipv4.tcp_*<\/code>.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Device settings<\/strong> \u2014 I\/O scheduler on each block device (<code>\/sys\/block\/*\/queue\/scheduler<\/code>), read-ahead (<code>read_ahead_kb<\/code>), and sometimes queue depth.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Plugin actions<\/strong> \u2014 CPU governor and energy\/performance preference via the <code>cpu<\/code> plugin, disk power management, and in some profiles, transparent hugepage behaviour.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Profiles live in <code>\/usr\/lib\/tuned\/&lt;name&gt;\/tuned.conf<\/code>, with local overrides in <code>\/etc\/tuned\/<\/code>. That split matters: your custom profile in <code>\/etc\/tuned\/<\/code> survives package upgrades, edits to <code>\/usr\/lib\/<\/code> do not.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Profile comparison for VPS workloads<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Profile<\/th><th>Intended workload<\/th><th>Key vm settings<\/th><th>I\/O scheduler<\/th><th>Fit for a VPS<\/th><\/tr><\/thead><tbody><tr><td><code>throughput-performance<\/code><\/td><td>Generic server, maximum I\/O and CPU<\/td><td>swappiness=10, dirty_ratio=40, dirty_background=10<\/td><td>mq-deadline \/ none<\/td><td>Good default for web or general servers<\/td><\/tr><tr><td><code>latency-performance<\/code><\/td><td>Latency-sensitive services<\/td><td>swappiness=10, dirty_ratio=10, dirty_background=3<\/td><td>none \/ mq-deadline<\/td><td>Best for databases on SSD\/NVMe<\/td><\/tr><tr><td><code>network-latency<\/code><\/td><td>Network-focused, low per-packet latency<\/td><td>swappiness=10, aggressive THP disabled<\/td><td>unchanged<\/td><td>Useful for proxy\/firewall roles<\/td><\/tr><tr><td><code>network-throughput<\/code><\/td><td>High bandwidth bulk transfer<\/td><td>large TCP buffers<\/td><td>unchanged<\/td><td>Niche; large socket buffers cost RAM<\/td><\/tr><tr><td><code>virtual-guest<\/code><\/td><td>Generic VM guest<\/td><td>moderate swappiness=30<\/td><td>none<\/td><td>Reasonable starting point, not optimal<\/td><\/tr><tr><td><code>balanced<\/code><\/td><td>Default desktop\/server mix<\/td><td>swappiness=60, dirty_ratio=20<\/td><td>mq-deadline<\/td><td>Too conservative for a busy VPS<\/td><\/tr><tr><td><code>powersave<\/code><\/td><td>Minimise energy<\/td><td>swappiness=60+<\/td><td>unchanged<\/td><td>Never on a server<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:paragraph -->\n<p>Two of those rows carry most of the value. <code>throughput-performance<\/code> is the right starting point for a general web VPS. <code>latency-performance<\/code> is the right choice when a database shares the box, because it forces dirty pages to disk far more eagerly (<code>dirty_ratio=10<\/code> instead of 40), which keeps write latency predictable at the cost of a little raw throughput. On a small NVMe VPS with a 256&nbsp;MB buffer pool, that trade is almost always correct.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Applying and verifying a profile<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile throughput-performance<br>tuned-adm active<br>tuned-adm verify<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm verify<\/code> re-reads every setting in the active profile and reports whether the live kernel state matches. If you (or a cloud-init script, or another tuning tool) changed a sysctl after tuned applied, verify will flag the drift. This is the main reason to run tuned rather than a hand-written sysctl file: it detects and can re-assert your configuration.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>To see what a profile will change before you apply it:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile_info latency-performance<br>tuned-adm recommend<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm recommend<\/code> uses the <code>recommend<\/code> plugin to inspect the host (virt type, whether it is a guest, disk type) and suggest a profile. On a KVM VPS it usually suggests <code>virtual-guest<\/code> \u2014 a safe, unambitious choice you will generally want to override.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Building a custom profile that survives upgrades<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>The practical pattern is to inherit the closest stock profile and add your own overrides. Create <code>\/etc\/tuned\/vps-db\/tuned.conf<\/code>:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>[main]<br>summary=Database-optimised profile for 2\u20134 GB VPS<br>include=latency-performance<br><br>[sysctl]<br>vm.dirty_writeback_centisecs=500<br>vm.dirty_expire_centisecs=1000<br>vm.vfs_cache_pressure=50<br>net.core.somaxconn=1024<br><br>[disk]<br>read_ahead_kb=1024<br>scheduler=none<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Then <code>tuned-adm profile vps-db<\/code>. The <code>include=<\/code> line pulls in everything from <code>latency-performance<\/code>, and your sections override or extend it. Values you do not specify are left as the parent set them.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Two cautions. First, do not set <code>vm.swappiness=0<\/code> on a small VPS: with no swap headroom you convert memory pressure directly into OOM kills instead of slow paging. <code>10<\/code> is the practical floor. Second, <code>scheduler=none<\/code> (or <code>mq-deadline<\/code>) is correct for SSDs; on spinning disk you want <code>bfq<\/code> or <code>mq-deadline<\/code> with a larger read-ahead. You can confirm the device is SSD with <code>cat \/sys\/block\/vda\/queue\/rotational<\/code> \u2014 0 means SSD.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Measuring whether it helped<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Changing a profile without measuring is guesswork. A minimal before\/after check on the database VPS:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>sysbench fileio --file-total-size=2G --file-test-mode=rndrw prepare<br>sysbench fileio --file-total-size=2G --file-test-mode=rndrw --time=60 run<br>vmstat 1 60 > \/tmp\/vmstat.after<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Watch <code>si<\/code>\/<code>so<\/code> (swap in\/out) and the <code>bo<\/code> column in <code>vmstat<\/code>. A latency profile should reduce bursty <code>bo<\/code> at the cost of slightly higher steady <code>bo<\/code>. If <code>si<\/code>\/<code>so<\/code> are non-zero in either run, your working set does not fit in RAM and no tunable profile will fix that \u2014 you need a bigger instance or a smaller buffer pool. Our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/diagnosing-vps-disk-io-latency-spikes\">diagnosing VPS disk I\/O latency spikes<\/a> covers how to tell scheduler-caused latency from genuine device saturation.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Practical recommendations<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:post-content --><!-- \/wp:list-item -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list-item -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list --><!-- wp:post-content --><!-- wp:paragraph -->\n<p><code>tuned<\/code> is one of the most useful and most misunderstood packages on a Linux VPS. It ships a set of named kernel and device profiles \u2014 curated bundles of CPU governor, scheduler, virtual memory, disk queue and power settings \u2014 and applies them atomically with <code>tuned-adm profile &lt;name&gt;<\/code>. The misunderstanding is treating profiles as magic performance buttons. They are not: a profile is a bundle of conservative tunings that must match your workload or it will actively hurt you. A database VPS running a web-optimised profile will have the wrong read-ahead and dirty-ratio settings; a web VPS running a throughput profile may hold dirty pages too long and spike latency on small writes.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>This article covers the profiles that matter on a VPS, what each one actually changes, how to verify it took effect, and how to build a custom profile that layers your own sysctl values on top. If you have not yet worked through generic kernel tuning, start with our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-kernel-parameters-sysctl-tuning\">VPS kernel parameter and sysctl tuning<\/a>, since a tuned profile is just a managed overlay on the same parameters.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>What tuned actually does<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Installed via <code>apt install tuned<\/code> (Debian\/Ubuntu) or <code>dnf install tuned<\/code> (RHEL family), tuned runs as a daemon with a profile selected at <code>\/etc\/tuned\/active_profile<\/code>. On activation it performs three kinds of changes:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li><strong>sysctl<\/strong> writes \u2014 values like <code>vm.swappiness<\/code>, <code>vm.dirty_ratio<\/code>, <code>vm.dirty_background_ratio<\/code>, <code>net.ipv4.tcp_*<\/code>.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Device settings<\/strong> \u2014 I\/O scheduler on each block device (<code>\/sys\/block\/*\/queue\/scheduler<\/code>), read-ahead (<code>read_ahead_kb<\/code>), and sometimes queue depth.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Plugin actions<\/strong> \u2014 CPU governor and energy\/performance preference via the <code>cpu<\/code> plugin, disk power management, and in some profiles, transparent hugepage behaviour.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Profiles live in <code>\/usr\/lib\/tuned\/&lt;name&gt;\/tuned.conf<\/code>, with local overrides in <code>\/etc\/tuned\/<\/code>. That split matters: your custom profile in <code>\/etc\/tuned\/<\/code> survives package upgrades, edits to <code>\/usr\/lib\/<\/code> do not.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Profile comparison for VPS workloads<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Profile<\/th><th>Intended workload<\/th><th>Key vm settings<\/th><th>I\/O scheduler<\/th><th>Fit for a VPS<\/th><\/tr><\/thead><tbody><tr><td><code>throughput-performance<\/code><\/td><td>Generic server, maximum I\/O and CPU<\/td><td>swappiness=10, dirty_ratio=40, dirty_background=10<\/td><td>mq-deadline \/ none<\/td><td>Good default for web or general servers<\/td><\/tr><tr><td><code>latency-performance<\/code><\/td><td>Latency-sensitive services<\/td><td>swappiness=10, dirty_ratio=10, dirty_background=3<\/td><td>none \/ mq-deadline<\/td><td>Best for databases on SSD\/NVMe<\/td><\/tr><tr><td><code>network-latency<\/code><\/td><td>Network-focused, low per-packet latency<\/td><td>swappiness=10, aggressive THP disabled<\/td><td>unchanged<\/td><td>Useful for proxy\/firewall roles<\/td><\/tr><tr><td><code>network-throughput<\/code><\/td><td>High bandwidth bulk transfer<\/td><td>large TCP buffers<\/td><td>unchanged<\/td><td>Niche; large socket buffers cost RAM<\/td><\/tr><tr><td><code>virtual-guest<\/code><\/td><td>Generic VM guest<\/td><td>moderate swappiness=30<\/td><td>none<\/td><td>Reasonable starting point, not optimal<\/td><\/tr><tr><td><code>balanced<\/code><\/td><td>Default desktop\/server mix<\/td><td>swappiness=60, dirty_ratio=20<\/td><td>mq-deadline<\/td><td>Too conservative for a busy VPS<\/td><\/tr><tr><td><code>powersave<\/code><\/td><td>Minimise energy<\/td><td>swappiness=60+<\/td><td>unchanged<\/td><td>Never on a server<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:paragraph -->\n<p>Two of those rows carry most of the value. <code>throughput-performance<\/code> is the right starting point for a general web VPS. <code>latency-performance<\/code> is the right choice when a database shares the box, because it forces dirty pages to disk far more eagerly (<code>dirty_ratio=10<\/code> instead of 40), which keeps write latency predictable at the cost of a little raw throughput. On a small NVMe VPS with a 256&nbsp;MB buffer pool, that trade is almost always correct.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Applying and verifying a profile<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile throughput-performance<br>tuned-adm active<br>tuned-adm verify<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm verify<\/code> re-reads every setting in the active profile and reports whether the live kernel state matches. If you (or a cloud-init script, or another tuning tool) changed a sysctl after tuned applied, verify will flag the drift. This is the main reason to run tuned rather than a hand-written sysctl file: it detects and can re-assert your configuration.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>To see what a profile will change before you apply it:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile_info latency-performance<br>tuned-adm recommend<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm recommend<\/code> uses the <code>recommend<\/code> plugin to inspect the host (virt type, whether it is a guest, disk type) and suggest a profile. On a KVM VPS it usually suggests <code>virtual-guest<\/code> \u2014 a safe, unambitious choice you will generally want to override.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Building a custom profile that survives upgrades<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>The practical pattern is to inherit the closest stock profile and add your own overrides. Create <code>\/etc\/tuned\/vps-db\/tuned.conf<\/code>:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>[main]<br>summary=Database-optimised profile for 2\u20134 GB VPS<br>include=latency-performance<br><br>[sysctl]<br>vm.dirty_writeback_centisecs=500<br>vm.dirty_expire_centisecs=1000<br>vm.vfs_cache_pressure=50<br>net.core.somaxconn=1024<br><br>[disk]<br>read_ahead_kb=1024<br>scheduler=none<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Then <code>tuned-adm profile vps-db<\/code>. The <code>include=<\/code> line pulls in everything from <code>latency-performance<\/code>, and your sections override or extend it. Values you do not specify are left as the parent set them.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Two cautions. First, do not set <code>vm.swappiness=0<\/code> on a small VPS: with no swap headroom you convert memory pressure directly into OOM kills instead of slow paging. <code>10<\/code> is the practical floor. Second, <code>scheduler=none<\/code> (or <code>mq-deadline<\/code>) is correct for SSDs; on spinning disk you want <code>bfq<\/code> or <code>mq-deadline<\/code> with a larger read-ahead. You can confirm the device is SSD with <code>cat \/sys\/block\/vda\/queue\/rotational<\/code> \u2014 0 means SSD.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Measuring whether it helped<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Changing a profile without measuring is guesswork. A minimal before\/after check on the database VPS:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>sysbench fileio --file-total-size=2G --file-test-mode=rndrw prepare<br>sysbench fileio --file-total-size=2G --file-test-mode=rndrw --time=60 run<br>vmstat 1 60 > \/tmp\/vmstat.after<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Watch <code>si<\/code>\/<code>so<\/code> (swap in\/out) and the <code>bo<\/code> column in <code>vmstat<\/code>. A latency profile should reduce bursty <code>bo<\/code> at the cost of slightly higher steady <code>bo<\/code>. If <code>si<\/code>\/<code>so<\/code> are non-zero in either run, your working set does not fit in RAM and no tunable profile will fix that \u2014 you need a bigger instance or a smaller buffer pool. Our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/diagnosing-vps-disk-io-latency-spikes\">diagnosing VPS disk I\/O latency spikes<\/a> covers how to tell scheduler-caused latency from genuine device saturation.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Practical recommendations<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:post-content --><!-- \/wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list-item -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:list-item --><!-- \/wp:list-item -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list-item -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list --><!-- wp:post-content --><!-- wp:paragraph -->\n<p><code>tuned<\/code> is one of the most useful and most misunderstood packages on a Linux VPS. It ships a set of named kernel and device profiles \u2014 curated bundles of CPU governor, scheduler, virtual memory, disk queue and power settings \u2014 and applies them atomically with <code>tuned-adm profile &lt;name&gt;<\/code>. The misunderstanding is treating profiles as magic performance buttons. They are not: a profile is a bundle of conservative tunings that must match your workload or it will actively hurt you. A database VPS running a web-optimised profile will have the wrong read-ahead and dirty-ratio settings; a web VPS running a throughput profile may hold dirty pages too long and spike latency on small writes.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>This article covers the profiles that matter on a VPS, what each one actually changes, how to verify it took effect, and how to build a custom profile that layers your own sysctl values on top. If you have not yet worked through generic kernel tuning, start with our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-kernel-parameters-sysctl-tuning\">VPS kernel parameter and sysctl tuning<\/a>, since a tuned profile is just a managed overlay on the same parameters.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>What tuned actually does<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Installed via <code>apt install tuned<\/code> (Debian\/Ubuntu) or <code>dnf install tuned<\/code> (RHEL family), tuned runs as a daemon with a profile selected at <code>\/etc\/tuned\/active_profile<\/code>. On activation it performs three kinds of changes:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li><strong>sysctl<\/strong> writes \u2014 values like <code>vm.swappiness<\/code>, <code>vm.dirty_ratio<\/code>, <code>vm.dirty_background_ratio<\/code>, <code>net.ipv4.tcp_*<\/code>.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Device settings<\/strong> \u2014 I\/O scheduler on each block device (<code>\/sys\/block\/*\/queue\/scheduler<\/code>), read-ahead (<code>read_ahead_kb<\/code>), and sometimes queue depth.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Plugin actions<\/strong> \u2014 CPU governor and energy\/performance preference via the <code>cpu<\/code> plugin, disk power management, and in some profiles, transparent hugepage behaviour.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Profiles live in <code>\/usr\/lib\/tuned\/&lt;name&gt;\/tuned.conf<\/code>, with local overrides in <code>\/etc\/tuned\/<\/code>. That split matters: your custom profile in <code>\/etc\/tuned\/<\/code> survives package upgrades, edits to <code>\/usr\/lib\/<\/code> do not.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Profile comparison for VPS workloads<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Profile<\/th><th>Intended workload<\/th><th>Key vm settings<\/th><th>I\/O scheduler<\/th><th>Fit for a VPS<\/th><\/tr><\/thead><tbody><tr><td><code>throughput-performance<\/code><\/td><td>Generic server, maximum I\/O and CPU<\/td><td>swappiness=10, dirty_ratio=40, dirty_background=10<\/td><td>mq-deadline \/ none<\/td><td>Good default for web or general servers<\/td><\/tr><tr><td><code>latency-performance<\/code><\/td><td>Latency-sensitive services<\/td><td>swappiness=10, dirty_ratio=10, dirty_background=3<\/td><td>none \/ mq-deadline<\/td><td>Best for databases on SSD\/NVMe<\/td><\/tr><tr><td><code>network-latency<\/code><\/td><td>Network-focused, low per-packet latency<\/td><td>swappiness=10, aggressive THP disabled<\/td><td>unchanged<\/td><td>Useful for proxy\/firewall roles<\/td><\/tr><tr><td><code>network-throughput<\/code><\/td><td>High bandwidth bulk transfer<\/td><td>large TCP buffers<\/td><td>unchanged<\/td><td>Niche; large socket buffers cost RAM<\/td><\/tr><tr><td><code>virtual-guest<\/code><\/td><td>Generic VM guest<\/td><td>moderate swappiness=30<\/td><td>none<\/td><td>Reasonable starting point, not optimal<\/td><\/tr><tr><td><code>balanced<\/code><\/td><td>Default desktop\/server mix<\/td><td>swappiness=60, dirty_ratio=20<\/td><td>mq-deadline<\/td><td>Too conservative for a busy VPS<\/td><\/tr><tr><td><code>powersave<\/code><\/td><td>Minimise energy<\/td><td>swappiness=60+<\/td><td>unchanged<\/td><td>Never on a server<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:paragraph -->\n<p>Two of those rows carry most of the value. <code>throughput-performance<\/code> is the right starting point for a general web VPS. <code>latency-performance<\/code> is the right choice when a database shares the box, because it forces dirty pages to disk far more eagerly (<code>dirty_ratio=10<\/code> instead of 40), which keeps write latency predictable at the cost of a little raw throughput. On a small NVMe VPS with a 256&nbsp;MB buffer pool, that trade is almost always correct.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Applying and verifying a profile<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile throughput-performance<br>tuned-adm active<br>tuned-adm verify<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm verify<\/code> re-reads every setting in the active profile and reports whether the live kernel state matches. If you (or a cloud-init script, or another tuning tool) changed a sysctl after tuned applied, verify will flag the drift. This is the main reason to run tuned rather than a hand-written sysctl file: it detects and can re-assert your configuration.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>To see what a profile will change before you apply it:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile_info latency-performance<br>tuned-adm recommend<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm recommend<\/code> uses the <code>recommend<\/code> plugin to inspect the host (virt type, whether it is a guest, disk type) and suggest a profile. On a KVM VPS it usually suggests <code>virtual-guest<\/code> \u2014 a safe, unambitious choice you will generally want to override.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Building a custom profile that survives upgrades<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>The practical pattern is to inherit the closest stock profile and add your own overrides. Create <code>\/etc\/tuned\/vps-db\/tuned.conf<\/code>:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>[main]<br>summary=Database-optimised profile for 2\u20134 GB VPS<br>include=latency-performance<br><br>[sysctl]<br>vm.dirty_writeback_centisecs=500<br>vm.dirty_expire_centisecs=1000<br>vm.vfs_cache_pressure=50<br>net.core.somaxconn=1024<br><br>[disk]<br>read_ahead_kb=1024<br>scheduler=none<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Then <code>tuned-adm profile vps-db<\/code>. The <code>include=<\/code> line pulls in everything from <code>latency-performance<\/code>, and your sections override or extend it. Values you do not specify are left as the parent set them.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Two cautions. First, do not set <code>vm.swappiness=0<\/code> on a small VPS: with no swap headroom you convert memory pressure directly into OOM kills instead of slow paging. <code>10<\/code> is the practical floor. Second, <code>scheduler=none<\/code> (or <code>mq-deadline<\/code>) is correct for SSDs; on spinning disk you want <code>bfq<\/code> or <code>mq-deadline<\/code> with a larger read-ahead. You can confirm the device is SSD with <code>cat \/sys\/block\/vda\/queue\/rotational<\/code> \u2014 0 means SSD.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Measuring whether it helped<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Changing a profile without measuring is guesswork. A minimal before\/after check on the database VPS:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>sysbench fileio --file-total-size=2G --file-test-mode=rndrw prepare<br>sysbench fileio --file-total-size=2G --file-test-mode=rndrw --time=60 run<br>vmstat 1 60 > \/tmp\/vmstat.after<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Watch <code>si<\/code>\/<code>so<\/code> (swap in\/out) and the <code>bo<\/code> column in <code>vmstat<\/code>. A latency profile should reduce bursty <code>bo<\/code> at the cost of slightly higher steady <code>bo<\/code>. If <code>si<\/code>\/<code>so<\/code> are non-zero in either run, your working set does not fit in RAM and no tunable profile will fix that \u2014 you need a bigger instance or a smaller buffer pool. Our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/diagnosing-vps-disk-io-latency-spikes\">diagnosing VPS disk I\/O latency spikes<\/a> covers how to tell scheduler-caused latency from genuine device saturation.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Practical recommendations<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:post-content --><!-- \/wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list-item -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list-item -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list --><!-- wp:post-content --><!-- wp:paragraph -->\n<p><code>tuned<\/code> is one of the most useful and most misunderstood packages on a Linux VPS. It ships a set of named kernel and device profiles \u2014 curated bundles of CPU governor, scheduler, virtual memory, disk queue and power settings \u2014 and applies them atomically with <code>tuned-adm profile &lt;name&gt;<\/code>. The misunderstanding is treating profiles as magic performance buttons. They are not: a profile is a bundle of conservative tunings that must match your workload or it will actively hurt you. A database VPS running a web-optimised profile will have the wrong read-ahead and dirty-ratio settings; a web VPS running a throughput profile may hold dirty pages too long and spike latency on small writes.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>This article covers the profiles that matter on a VPS, what each one actually changes, how to verify it took effect, and how to build a custom profile that layers your own sysctl values on top. If you have not yet worked through generic kernel tuning, start with our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-kernel-parameters-sysctl-tuning\">VPS kernel parameter and sysctl tuning<\/a>, since a tuned profile is just a managed overlay on the same parameters.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>What tuned actually does<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Installed via <code>apt install tuned<\/code> (Debian\/Ubuntu) or <code>dnf install tuned<\/code> (RHEL family), tuned runs as a daemon with a profile selected at <code>\/etc\/tuned\/active_profile<\/code>. On activation it performs three kinds of changes:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li><strong>sysctl<\/strong> writes \u2014 values like <code>vm.swappiness<\/code>, <code>vm.dirty_ratio<\/code>, <code>vm.dirty_background_ratio<\/code>, <code>net.ipv4.tcp_*<\/code>.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Device settings<\/strong> \u2014 I\/O scheduler on each block device (<code>\/sys\/block\/*\/queue\/scheduler<\/code>), read-ahead (<code>read_ahead_kb<\/code>), and sometimes queue depth.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Plugin actions<\/strong> \u2014 CPU governor and energy\/performance preference via the <code>cpu<\/code> plugin, disk power management, and in some profiles, transparent hugepage behaviour.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Profiles live in <code>\/usr\/lib\/tuned\/&lt;name&gt;\/tuned.conf<\/code>, with local overrides in <code>\/etc\/tuned\/<\/code>. That split matters: your custom profile in <code>\/etc\/tuned\/<\/code> survives package upgrades, edits to <code>\/usr\/lib\/<\/code> do not.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Profile comparison for VPS workloads<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Profile<\/th><th>Intended workload<\/th><th>Key vm settings<\/th><th>I\/O scheduler<\/th><th>Fit for a VPS<\/th><\/tr><\/thead><tbody><tr><td><code>throughput-performance<\/code><\/td><td>Generic server, maximum I\/O and CPU<\/td><td>swappiness=10, dirty_ratio=40, dirty_background=10<\/td><td>mq-deadline \/ none<\/td><td>Good default for web or general servers<\/td><\/tr><tr><td><code>latency-performance<\/code><\/td><td>Latency-sensitive services<\/td><td>swappiness=10, dirty_ratio=10, dirty_background=3<\/td><td>none \/ mq-deadline<\/td><td>Best for databases on SSD\/NVMe<\/td><\/tr><tr><td><code>network-latency<\/code><\/td><td>Network-focused, low per-packet latency<\/td><td>swappiness=10, aggressive THP disabled<\/td><td>unchanged<\/td><td>Useful for proxy\/firewall roles<\/td><\/tr><tr><td><code>network-throughput<\/code><\/td><td>High bandwidth bulk transfer<\/td><td>large TCP buffers<\/td><td>unchanged<\/td><td>Niche; large socket buffers cost RAM<\/td><\/tr><tr><td><code>virtual-guest<\/code><\/td><td>Generic VM guest<\/td><td>moderate swappiness=30<\/td><td>none<\/td><td>Reasonable starting point, not optimal<\/td><\/tr><tr><td><code>balanced<\/code><\/td><td>Default desktop\/server mix<\/td><td>swappiness=60, dirty_ratio=20<\/td><td>mq-deadline<\/td><td>Too conservative for a busy VPS<\/td><\/tr><tr><td><code>powersave<\/code><\/td><td>Minimise energy<\/td><td>swappiness=60+<\/td><td>unchanged<\/td><td>Never on a server<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:paragraph -->\n<p>Two of those rows carry most of the value. <code>throughput-performance<\/code> is the right starting point for a general web VPS. <code>latency-performance<\/code> is the right choice when a database shares the box, because it forces dirty pages to disk far more eagerly (<code>dirty_ratio=10<\/code> instead of 40), which keeps write latency predictable at the cost of a little raw throughput. On a small NVMe VPS with a 256&nbsp;MB buffer pool, that trade is almost always correct.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Applying and verifying a profile<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile throughput-performance<br>tuned-adm active<br>tuned-adm verify<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm verify<\/code> re-reads every setting in the active profile and reports whether the live kernel state matches. If you (or a cloud-init script, or another tuning tool) changed a sysctl after tuned applied, verify will flag the drift. This is the main reason to run tuned rather than a hand-written sysctl file: it detects and can re-assert your configuration.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>To see what a profile will change before you apply it:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile_info latency-performance<br>tuned-adm recommend<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm recommend<\/code> uses the <code>recommend<\/code> plugin to inspect the host (virt type, whether it is a guest, disk type) and suggest a profile. On a KVM VPS it usually suggests <code>virtual-guest<\/code> \u2014 a safe, unambitious choice you will generally want to override.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Building a custom profile that survives upgrades<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>The practical pattern is to inherit the closest stock profile and add your own overrides. Create <code>\/etc\/tuned\/vps-db\/tuned.conf<\/code>:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>[main]<br>summary=Database-optimised profile for 2\u20134 GB VPS<br>include=latency-performance<br><br>[sysctl]<br>vm.dirty_writeback_centisecs=500<br>vm.dirty_expire_centisecs=1000<br>vm.vfs_cache_pressure=50<br>net.core.somaxconn=1024<br><br>[disk]<br>read_ahead_kb=1024<br>scheduler=none<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Then <code>tuned-adm profile vps-db<\/code>. The <code>include=<\/code> line pulls in everything from <code>latency-performance<\/code>, and your sections override or extend it. Values you do not specify are left as the parent set them.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Two cautions. First, do not set <code>vm.swappiness=0<\/code> on a small VPS: with no swap headroom you convert memory pressure directly into OOM kills instead of slow paging. <code>10<\/code> is the practical floor. Second, <code>scheduler=none<\/code> (or <code>mq-deadline<\/code>) is correct for SSDs; on spinning disk you want <code>bfq<\/code> or <code>mq-deadline<\/code> with a larger read-ahead. You can confirm the device is SSD with <code>cat \/sys\/block\/vda\/queue\/rotational<\/code> \u2014 0 means SSD.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Measuring whether it helped<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Changing a profile without measuring is guesswork. A minimal before\/after check on the database VPS:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>sysbench fileio --file-total-size=2G --file-test-mode=rndrw prepare<br>sysbench fileio --file-total-size=2G --file-test-mode=rndrw --time=60 run<br>vmstat 1 60 > \/tmp\/vmstat.after<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Watch <code>si<\/code>\/<code>so<\/code> (swap in\/out) and the <code>bo<\/code> column in <code>vmstat<\/code>. A latency profile should reduce bursty <code>bo<\/code> at the cost of slightly higher steady <code>bo<\/code>. If <code>si<\/code>\/<code>so<\/code> are non-zero in either run, your working set does not fit in RAM and no tunable profile will fix that \u2014 you need a bigger instance or a smaller buffer pool. Our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/diagnosing-vps-disk-io-latency-spikes\">diagnosing VPS disk I\/O latency spikes<\/a> covers how to tell scheduler-caused latency from genuine device saturation.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Practical recommendations<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:post-content --><!-- \/wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list-item -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list-item -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list --><!-- wp:post-content --><!-- wp:paragraph -->\n<p><code>tuned<\/code> is one of the most useful and most misunderstood packages on a Linux VPS. It ships a set of named kernel and device profiles \u2014 curated bundles of CPU governor, scheduler, virtual memory, disk queue and power settings \u2014 and applies them atomically with <code>tuned-adm profile &lt;name&gt;<\/code>. The misunderstanding is treating profiles as magic performance buttons. They are not: a profile is a bundle of conservative tunings that must match your workload or it will actively hurt you. A database VPS running a web-optimised profile will have the wrong read-ahead and dirty-ratio settings; a web VPS running a throughput profile may hold dirty pages too long and spike latency on small writes.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>This article covers the profiles that matter on a VPS, what each one actually changes, how to verify it took effect, and how to build a custom profile that layers your own sysctl values on top. If you have not yet worked through generic kernel tuning, start with our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-kernel-parameters-sysctl-tuning\">VPS kernel parameter and sysctl tuning<\/a>, since a tuned profile is just a managed overlay on the same parameters.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>What tuned actually does<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Installed via <code>apt install tuned<\/code> (Debian\/Ubuntu) or <code>dnf install tuned<\/code> (RHEL family), tuned runs as a daemon with a profile selected at <code>\/etc\/tuned\/active_profile<\/code>. On activation it performs three kinds of changes:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li><strong>sysctl<\/strong> writes \u2014 values like <code>vm.swappiness<\/code>, <code>vm.dirty_ratio<\/code>, <code>vm.dirty_background_ratio<\/code>, <code>net.ipv4.tcp_*<\/code>.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Device settings<\/strong> \u2014 I\/O scheduler on each block device (<code>\/sys\/block\/*\/queue\/scheduler<\/code>), read-ahead (<code>read_ahead_kb<\/code>), and sometimes queue depth.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Plugin actions<\/strong> \u2014 CPU governor and energy\/performance preference via the <code>cpu<\/code> plugin, disk power management, and in some profiles, transparent hugepage behaviour.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Profiles live in <code>\/usr\/lib\/tuned\/&lt;name&gt;\/tuned.conf<\/code>, with local overrides in <code>\/etc\/tuned\/<\/code>. That split matters: your custom profile in <code>\/etc\/tuned\/<\/code> survives package upgrades, edits to <code>\/usr\/lib\/<\/code> do not.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Profile comparison for VPS workloads<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Profile<\/th><th>Intended workload<\/th><th>Key vm settings<\/th><th>I\/O scheduler<\/th><th>Fit for a VPS<\/th><\/tr><\/thead><tbody><tr><td><code>throughput-performance<\/code><\/td><td>Generic server, maximum I\/O and CPU<\/td><td>swappiness=10, dirty_ratio=40, dirty_background=10<\/td><td>mq-deadline \/ none<\/td><td>Good default for web or general servers<\/td><\/tr><tr><td><code>latency-performance<\/code><\/td><td>Latency-sensitive services<\/td><td>swappiness=10, dirty_ratio=10, dirty_background=3<\/td><td>none \/ mq-deadline<\/td><td>Best for databases on SSD\/NVMe<\/td><\/tr><tr><td><code>network-latency<\/code><\/td><td>Network-focused, low per-packet latency<\/td><td>swappiness=10, aggressive THP disabled<\/td><td>unchanged<\/td><td>Useful for proxy\/firewall roles<\/td><\/tr><tr><td><code>network-throughput<\/code><\/td><td>High bandwidth bulk transfer<\/td><td>large TCP buffers<\/td><td>unchanged<\/td><td>Niche; large socket buffers cost RAM<\/td><\/tr><tr><td><code>virtual-guest<\/code><\/td><td>Generic VM guest<\/td><td>moderate swappiness=30<\/td><td>none<\/td><td>Reasonable starting point, not optimal<\/td><\/tr><tr><td><code>balanced<\/code><\/td><td>Default desktop\/server mix<\/td><td>swappiness=60, dirty_ratio=20<\/td><td>mq-deadline<\/td><td>Too conservative for a busy VPS<\/td><\/tr><tr><td><code>powersave<\/code><\/td><td>Minimise energy<\/td><td>swappiness=60+<\/td><td>unchanged<\/td><td>Never on a server<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:paragraph -->\n<p>Two of those rows carry most of the value. <code>throughput-performance<\/code> is the right starting point for a general web VPS. <code>latency-performance<\/code> is the right choice when a database shares the box, because it forces dirty pages to disk far more eagerly (<code>dirty_ratio=10<\/code> instead of 40), which keeps write latency predictable at the cost of a little raw throughput. On a small NVMe VPS with a 256&nbsp;MB buffer pool, that trade is almost always correct.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Applying and verifying a profile<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile throughput-performance<br>tuned-adm active<br>tuned-adm verify<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm verify<\/code> re-reads every setting in the active profile and reports whether the live kernel state matches. If you (or a cloud-init script, or another tuning tool) changed a sysctl after tuned applied, verify will flag the drift. This is the main reason to run tuned rather than a hand-written sysctl file: it detects and can re-assert your configuration.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>To see what a profile will change before you apply it:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile_info latency-performance<br>tuned-adm recommend<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm recommend<\/code> uses the <code>recommend<\/code> plugin to inspect the host (virt type, whether it is a guest, disk type) and suggest a profile. On a KVM VPS it usually suggests <code>virtual-guest<\/code> \u2014 a safe, unambitious choice you will generally want to override.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Building a custom profile that survives upgrades<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>The practical pattern is to inherit the closest stock profile and add your own overrides. Create <code>\/etc\/tuned\/vps-db\/tuned.conf<\/code>:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>[main]<br>summary=Database-optimised profile for 2\u20134 GB VPS<br>include=latency-performance<br><br>[sysctl]<br>vm.dirty_writeback_centisecs=500<br>vm.dirty_expire_centisecs=1000<br>vm.vfs_cache_pressure=50<br>net.core.somaxconn=1024<br><br>[disk]<br>read_ahead_kb=1024<br>scheduler=none<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Then <code>tuned-adm profile vps-db<\/code>. The <code>include=<\/code> line pulls in everything from <code>latency-performance<\/code>, and your sections override or extend it. Values you do not specify are left as the parent set them.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Two cautions. First, do not set <code>vm.swappiness=0<\/code> on a small VPS: with no swap headroom you convert memory pressure directly into OOM kills instead of slow paging. <code>10<\/code> is the practical floor. Second, <code>scheduler=none<\/code> (or <code>mq-deadline<\/code>) is correct for SSDs; on spinning disk you want <code>bfq<\/code> or <code>mq-deadline<\/code> with a larger read-ahead. You can confirm the device is SSD with <code>cat \/sys\/block\/vda\/queue\/rotational<\/code> \u2014 0 means SSD.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Measuring whether it helped<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Changing a profile without measuring is guesswork. A minimal before\/after check on the database VPS:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>sysbench fileio --file-total-size=2G --file-test-mode=rndrw prepare<br>sysbench fileio --file-total-size=2G --file-test-mode=rndrw --time=60 run<br>vmstat 1 60 > \/tmp\/vmstat.after<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Watch <code>si<\/code>\/<code>so<\/code> (swap in\/out) and the <code>bo<\/code> column in <code>vmstat<\/code>. A latency profile should reduce bursty <code>bo<\/code> at the cost of slightly higher steady <code>bo<\/code>. If <code>si<\/code>\/<code>so<\/code> are non-zero in either run, your working set does not fit in RAM and no tunable profile will fix that \u2014 you need a bigger instance or a smaller buffer pool. Our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/diagnosing-vps-disk-io-latency-spikes\">diagnosing VPS disk I\/O latency spikes<\/a> covers how to tell scheduler-caused latency from genuine device saturation.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Practical recommendations<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:post-content --><!-- \/wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list-item -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list-item -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list --><!-- wp:post-content --><!-- wp:paragraph -->\n<p><code>tuned<\/code> is one of the most useful and most misunderstood packages on a Linux VPS. It ships a set of named kernel and device profiles \u2014 curated bundles of CPU governor, scheduler, virtual memory, disk queue and power settings \u2014 and applies them atomically with <code>tuned-adm profile &lt;name&gt;<\/code>. The misunderstanding is treating profiles as magic performance buttons. They are not: a profile is a bundle of conservative tunings that must match your workload or it will actively hurt you. A database VPS running a web-optimised profile will have the wrong read-ahead and dirty-ratio settings; a web VPS running a throughput profile may hold dirty pages too long and spike latency on small writes.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>This article covers the profiles that matter on a VPS, what each one actually changes, how to verify it took effect, and how to build a custom profile that layers your own sysctl values on top. If you have not yet worked through generic kernel tuning, start with our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-kernel-parameters-sysctl-tuning\">VPS kernel parameter and sysctl tuning<\/a>, since a tuned profile is just a managed overlay on the same parameters.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>What tuned actually does<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Installed via <code>apt install tuned<\/code> (Debian\/Ubuntu) or <code>dnf install tuned<\/code> (RHEL family), tuned runs as a daemon with a profile selected at <code>\/etc\/tuned\/active_profile<\/code>. On activation it performs three kinds of changes:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li><strong>sysctl<\/strong> writes \u2014 values like <code>vm.swappiness<\/code>, <code>vm.dirty_ratio<\/code>, <code>vm.dirty_background_ratio<\/code>, <code>net.ipv4.tcp_*<\/code>.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Device settings<\/strong> \u2014 I\/O scheduler on each block device (<code>\/sys\/block\/*\/queue\/scheduler<\/code>), read-ahead (<code>read_ahead_kb<\/code>), and sometimes queue depth.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Plugin actions<\/strong> \u2014 CPU governor and energy\/performance preference via the <code>cpu<\/code> plugin, disk power management, and in some profiles, transparent hugepage behaviour.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Profiles live in <code>\/usr\/lib\/tuned\/&lt;name&gt;\/tuned.conf<\/code>, with local overrides in <code>\/etc\/tuned\/<\/code>. That split matters: your custom profile in <code>\/etc\/tuned\/<\/code> survives package upgrades, edits to <code>\/usr\/lib\/<\/code> do not.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Profile comparison for VPS workloads<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Profile<\/th><th>Intended workload<\/th><th>Key vm settings<\/th><th>I\/O scheduler<\/th><th>Fit for a VPS<\/th><\/tr><\/thead><tbody><tr><td><code>throughput-performance<\/code><\/td><td>Generic server, maximum I\/O and CPU<\/td><td>swappiness=10, dirty_ratio=40, dirty_background=10<\/td><td>mq-deadline \/ none<\/td><td>Good default for web or general servers<\/td><\/tr><tr><td><code>latency-performance<\/code><\/td><td>Latency-sensitive services<\/td><td>swappiness=10, dirty_ratio=10, dirty_background=3<\/td><td>none \/ mq-deadline<\/td><td>Best for databases on SSD\/NVMe<\/td><\/tr><tr><td><code>network-latency<\/code><\/td><td>Network-focused, low per-packet latency<\/td><td>swappiness=10, aggressive THP disabled<\/td><td>unchanged<\/td><td>Useful for proxy\/firewall roles<\/td><\/tr><tr><td><code>network-throughput<\/code><\/td><td>High bandwidth bulk transfer<\/td><td>large TCP buffers<\/td><td>unchanged<\/td><td>Niche; large socket buffers cost RAM<\/td><\/tr><tr><td><code>virtual-guest<\/code><\/td><td>Generic VM guest<\/td><td>moderate swappiness=30<\/td><td>none<\/td><td>Reasonable starting point, not optimal<\/td><\/tr><tr><td><code>balanced<\/code><\/td><td>Default desktop\/server mix<\/td><td>swappiness=60, dirty_ratio=20<\/td><td>mq-deadline<\/td><td>Too conservative for a busy VPS<\/td><\/tr><tr><td><code>powersave<\/code><\/td><td>Minimise energy<\/td><td>swappiness=60+<\/td><td>unchanged<\/td><td>Never on a server<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:paragraph -->\n<p>Two of those rows carry most of the value. <code>throughput-performance<\/code> is the right starting point for a general web VPS. <code>latency-performance<\/code> is the right choice when a database shares the box, because it forces dirty pages to disk far more eagerly (<code>dirty_ratio=10<\/code> instead of 40), which keeps write latency predictable at the cost of a little raw throughput. On a small NVMe VPS with a 256&nbsp;MB buffer pool, that trade is almost always correct.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Applying and verifying a profile<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile throughput-performance<br>tuned-adm active<br>tuned-adm verify<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm verify<\/code> re-reads every setting in the active profile and reports whether the live kernel state matches. If you (or a cloud-init script, or another tuning tool) changed a sysctl after tuned applied, verify will flag the drift. This is the main reason to run tuned rather than a hand-written sysctl file: it detects and can re-assert your configuration.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>To see what a profile will change before you apply it:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile_info latency-performance<br>tuned-adm recommend<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm recommend<\/code> uses the <code>recommend<\/code> plugin to inspect the host (virt type, whether it is a guest, disk type) and suggest a profile. On a KVM VPS it usually suggests <code>virtual-guest<\/code> \u2014 a safe, unambitious choice you will generally want to override.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Building a custom profile that survives upgrades<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>The practical pattern is to inherit the closest stock profile and add your own overrides. Create <code>\/etc\/tuned\/vps-db\/tuned.conf<\/code>:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>[main]<br>summary=Database-optimised profile for 2\u20134 GB VPS<br>include=latency-performance<br><br>[sysctl]<br>vm.dirty_writeback_centisecs=500<br>vm.dirty_expire_centisecs=1000<br>vm.vfs_cache_pressure=50<br>net.core.somaxconn=1024<br><br>[disk]<br>read_ahead_kb=1024<br>scheduler=none<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Then <code>tuned-adm profile vps-db<\/code>. The <code>include=<\/code> line pulls in everything from <code>latency-performance<\/code>, and your sections override or extend it. Values you do not specify are left as the parent set them.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Two cautions. First, do not set <code>vm.swappiness=0<\/code> on a small VPS: with no swap headroom you convert memory pressure directly into OOM kills instead of slow paging. <code>10<\/code> is the practical floor. Second, <code>scheduler=none<\/code> (or <code>mq-deadline<\/code>) is correct for SSDs; on spinning disk you want <code>bfq<\/code> or <code>mq-deadline<\/code> with a larger read-ahead. You can confirm the device is SSD with <code>cat \/sys\/block\/vda\/queue\/rotational<\/code> \u2014 0 means SSD.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Measuring whether it helped<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Changing a profile without measuring is guesswork. A minimal before\/after check on the database VPS:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>sysbench fileio --file-total-size=2G --file-test-mode=rndrw prepare<br>sysbench fileio --file-total-size=2G --file-test-mode=rndrw --time=60 run<br>vmstat 1 60 > \/tmp\/vmstat.after<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Watch <code>si<\/code>\/<code>so<\/code> (swap in\/out) and the <code>bo<\/code> column in <code>vmstat<\/code>. A latency profile should reduce bursty <code>bo<\/code> at the cost of slightly higher steady <code>bo<\/code>. If <code>si<\/code>\/<code>so<\/code> are non-zero in either run, your working set does not fit in RAM and no tunable profile will fix that \u2014 you need a bigger instance or a smaller buffer pool. Our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/diagnosing-vps-disk-io-latency-spikes\">diagnosing VPS disk I\/O latency spikes<\/a> covers how to tell scheduler-caused latency from genuine device saturation.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Practical recommendations<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:post-content --><!-- \/wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list-item -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:list-item --><!-- \/wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list-item -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list-item -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list --><!-- wp:post-content --><!-- wp:paragraph -->\n<p><code>tuned<\/code> is one of the most useful and most misunderstood packages on a Linux VPS. It ships a set of named kernel and device profiles \u2014 curated bundles of CPU governor, scheduler, virtual memory, disk queue and power settings \u2014 and applies them atomically with <code>tuned-adm profile &lt;name&gt;<\/code>. The misunderstanding is treating profiles as magic performance buttons. They are not: a profile is a bundle of conservative tunings that must match your workload or it will actively hurt you. A database VPS running a web-optimised profile will have the wrong read-ahead and dirty-ratio settings; a web VPS running a throughput profile may hold dirty pages too long and spike latency on small writes.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>This article covers the profiles that matter on a VPS, what each one actually changes, how to verify it took effect, and how to build a custom profile that layers your own sysctl values on top. If you have not yet worked through generic kernel tuning, start with our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-kernel-parameters-sysctl-tuning\">VPS kernel parameter and sysctl tuning<\/a>, since a tuned profile is just a managed overlay on the same parameters.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>What tuned actually does<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Installed via <code>apt install tuned<\/code> (Debian\/Ubuntu) or <code>dnf install tuned<\/code> (RHEL family), tuned runs as a daemon with a profile selected at <code>\/etc\/tuned\/active_profile<\/code>. On activation it performs three kinds of changes:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li><strong>sysctl<\/strong> writes \u2014 values like <code>vm.swappiness<\/code>, <code>vm.dirty_ratio<\/code>, <code>vm.dirty_background_ratio<\/code>, <code>net.ipv4.tcp_*<\/code>.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Device settings<\/strong> \u2014 I\/O scheduler on each block device (<code>\/sys\/block\/*\/queue\/scheduler<\/code>), read-ahead (<code>read_ahead_kb<\/code>), and sometimes queue depth.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Plugin actions<\/strong> \u2014 CPU governor and energy\/performance preference via the <code>cpu<\/code> plugin, disk power management, and in some profiles, transparent hugepage behaviour.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Profiles live in <code>\/usr\/lib\/tuned\/&lt;name&gt;\/tuned.conf<\/code>, with local overrides in <code>\/etc\/tuned\/<\/code>. That split matters: your custom profile in <code>\/etc\/tuned\/<\/code> survives package upgrades, edits to <code>\/usr\/lib\/<\/code> do not.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Profile comparison for VPS workloads<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Profile<\/th><th>Intended workload<\/th><th>Key vm settings<\/th><th>I\/O scheduler<\/th><th>Fit for a VPS<\/th><\/tr><\/thead><tbody><tr><td><code>throughput-performance<\/code><\/td><td>Generic server, maximum I\/O and CPU<\/td><td>swappiness=10, dirty_ratio=40, dirty_background=10<\/td><td>mq-deadline \/ none<\/td><td>Good default for web or general servers<\/td><\/tr><tr><td><code>latency-performance<\/code><\/td><td>Latency-sensitive services<\/td><td>swappiness=10, dirty_ratio=10, dirty_background=3<\/td><td>none \/ mq-deadline<\/td><td>Best for databases on SSD\/NVMe<\/td><\/tr><tr><td><code>network-latency<\/code><\/td><td>Network-focused, low per-packet latency<\/td><td>swappiness=10, aggressive THP disabled<\/td><td>unchanged<\/td><td>Useful for proxy\/firewall roles<\/td><\/tr><tr><td><code>network-throughput<\/code><\/td><td>High bandwidth bulk transfer<\/td><td>large TCP buffers<\/td><td>unchanged<\/td><td>Niche; large socket buffers cost RAM<\/td><\/tr><tr><td><code>virtual-guest<\/code><\/td><td>Generic VM guest<\/td><td>moderate swappiness=30<\/td><td>none<\/td><td>Reasonable starting point, not optimal<\/td><\/tr><tr><td><code>balanced<\/code><\/td><td>Default desktop\/server mix<\/td><td>swappiness=60, dirty_ratio=20<\/td><td>mq-deadline<\/td><td>Too conservative for a busy VPS<\/td><\/tr><tr><td><code>powersave<\/code><\/td><td>Minimise energy<\/td><td>swappiness=60+<\/td><td>unchanged<\/td><td>Never on a server<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:paragraph -->\n<p>Two of those rows carry most of the value. <code>throughput-performance<\/code> is the right starting point for a general web VPS. <code>latency-performance<\/code> is the right choice when a database shares the box, because it forces dirty pages to disk far more eagerly (<code>dirty_ratio=10<\/code> instead of 40), which keeps write latency predictable at the cost of a little raw throughput. On a small NVMe VPS with a 256&nbsp;MB buffer pool, that trade is almost always correct.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Applying and verifying a profile<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile throughput-performance<br>tuned-adm active<br>tuned-adm verify<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm verify<\/code> re-reads every setting in the active profile and reports whether the live kernel state matches. If you (or a cloud-init script, or another tuning tool) changed a sysctl after tuned applied, verify will flag the drift. This is the main reason to run tuned rather than a hand-written sysctl file: it detects and can re-assert your configuration.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>To see what a profile will change before you apply it:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile_info latency-performance<br>tuned-adm recommend<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm recommend<\/code> uses the <code>recommend<\/code> plugin to inspect the host (virt type, whether it is a guest, disk type) and suggest a profile. On a KVM VPS it usually suggests <code>virtual-guest<\/code> \u2014 a safe, unambitious choice you will generally want to override.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Building a custom profile that survives upgrades<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>The practical pattern is to inherit the closest stock profile and add your own overrides. Create <code>\/etc\/tuned\/vps-db\/tuned.conf<\/code>:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>[main]<br>summary=Database-optimised profile for 2\u20134 GB VPS<br>include=latency-performance<br><br>[sysctl]<br>vm.dirty_writeback_centisecs=500<br>vm.dirty_expire_centisecs=1000<br>vm.vfs_cache_pressure=50<br>net.core.somaxconn=1024<br><br>[disk]<br>read_ahead_kb=1024<br>scheduler=none<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Then <code>tuned-adm profile vps-db<\/code>. The <code>include=<\/code> line pulls in everything from <code>latency-performance<\/code>, and your sections override or extend it. Values you do not specify are left as the parent set them.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Two cautions. First, do not set <code>vm.swappiness=0<\/code> on a small VPS: with no swap headroom you convert memory pressure directly into OOM kills instead of slow paging. <code>10<\/code> is the practical floor. Second, <code>scheduler=none<\/code> (or <code>mq-deadline<\/code>) is correct for SSDs; on spinning disk you want <code>bfq<\/code> or <code>mq-deadline<\/code> with a larger read-ahead. You can confirm the device is SSD with <code>cat \/sys\/block\/vda\/queue\/rotational<\/code> \u2014 0 means SSD.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Measuring whether it helped<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Changing a profile without measuring is guesswork. A minimal before\/after check on the database VPS:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>sysbench fileio --file-total-size=2G --file-test-mode=rndrw prepare<br>sysbench fileio --file-total-size=2G --file-test-mode=rndrw --time=60 run<br>vmstat 1 60 > \/tmp\/vmstat.after<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Watch <code>si<\/code>\/<code>so<\/code> (swap in\/out) and the <code>bo<\/code> column in <code>vmstat<\/code>. A latency profile should reduce bursty <code>bo<\/code> at the cost of slightly higher steady <code>bo<\/code>. If <code>si<\/code>\/<code>so<\/code> are non-zero in either run, your working set does not fit in RAM and no tunable profile will fix that \u2014 you need a bigger instance or a smaller buffer pool. Our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/diagnosing-vps-disk-io-latency-spikes\">diagnosing VPS disk I\/O latency spikes<\/a> covers how to tell scheduler-caused latency from genuine device saturation.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Practical recommendations<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:post-content --><!-- \/wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list-item -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list-item -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list --><!-- wp:post-content --><!-- wp:paragraph -->\n<p><code>tuned<\/code> is one of the most useful and most misunderstood packages on a Linux VPS. It ships a set of named kernel and device profiles \u2014 curated bundles of CPU governor, scheduler, virtual memory, disk queue and power settings \u2014 and applies them atomically with <code>tuned-adm profile &lt;name&gt;<\/code>. The misunderstanding is treating profiles as magic performance buttons. They are not: a profile is a bundle of conservative tunings that must match your workload or it will actively hurt you. A database VPS running a web-optimised profile will have the wrong read-ahead and dirty-ratio settings; a web VPS running a throughput profile may hold dirty pages too long and spike latency on small writes.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>This article covers the profiles that matter on a VPS, what each one actually changes, how to verify it took effect, and how to build a custom profile that layers your own sysctl values on top. If you have not yet worked through generic kernel tuning, start with our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-kernel-parameters-sysctl-tuning\">VPS kernel parameter and sysctl tuning<\/a>, since a tuned profile is just a managed overlay on the same parameters.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>What tuned actually does<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Installed via <code>apt install tuned<\/code> (Debian\/Ubuntu) or <code>dnf install tuned<\/code> (RHEL family), tuned runs as a daemon with a profile selected at <code>\/etc\/tuned\/active_profile<\/code>. On activation it performs three kinds of changes:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li><strong>sysctl<\/strong> writes \u2014 values like <code>vm.swappiness<\/code>, <code>vm.dirty_ratio<\/code>, <code>vm.dirty_background_ratio<\/code>, <code>net.ipv4.tcp_*<\/code>.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Device settings<\/strong> \u2014 I\/O scheduler on each block device (<code>\/sys\/block\/*\/queue\/scheduler<\/code>), read-ahead (<code>read_ahead_kb<\/code>), and sometimes queue depth.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Plugin actions<\/strong> \u2014 CPU governor and energy\/performance preference via the <code>cpu<\/code> plugin, disk power management, and in some profiles, transparent hugepage behaviour.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Profiles live in <code>\/usr\/lib\/tuned\/&lt;name&gt;\/tuned.conf<\/code>, with local overrides in <code>\/etc\/tuned\/<\/code>. That split matters: your custom profile in <code>\/etc\/tuned\/<\/code> survives package upgrades, edits to <code>\/usr\/lib\/<\/code> do not.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Profile comparison for VPS workloads<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Profile<\/th><th>Intended workload<\/th><th>Key vm settings<\/th><th>I\/O scheduler<\/th><th>Fit for a VPS<\/th><\/tr><\/thead><tbody><tr><td><code>throughput-performance<\/code><\/td><td>Generic server, maximum I\/O and CPU<\/td><td>swappiness=10, dirty_ratio=40, dirty_background=10<\/td><td>mq-deadline \/ none<\/td><td>Good default for web or general servers<\/td><\/tr><tr><td><code>latency-performance<\/code><\/td><td>Latency-sensitive services<\/td><td>swappiness=10, dirty_ratio=10, dirty_background=3<\/td><td>none \/ mq-deadline<\/td><td>Best for databases on SSD\/NVMe<\/td><\/tr><tr><td><code>network-latency<\/code><\/td><td>Network-focused, low per-packet latency<\/td><td>swappiness=10, aggressive THP disabled<\/td><td>unchanged<\/td><td>Useful for proxy\/firewall roles<\/td><\/tr><tr><td><code>network-throughput<\/code><\/td><td>High bandwidth bulk transfer<\/td><td>large TCP buffers<\/td><td>unchanged<\/td><td>Niche; large socket buffers cost RAM<\/td><\/tr><tr><td><code>virtual-guest<\/code><\/td><td>Generic VM guest<\/td><td>moderate swappiness=30<\/td><td>none<\/td><td>Reasonable starting point, not optimal<\/td><\/tr><tr><td><code>balanced<\/code><\/td><td>Default desktop\/server mix<\/td><td>swappiness=60, dirty_ratio=20<\/td><td>mq-deadline<\/td><td>Too conservative for a busy VPS<\/td><\/tr><tr><td><code>powersave<\/code><\/td><td>Minimise energy<\/td><td>swappiness=60+<\/td><td>unchanged<\/td><td>Never on a server<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:paragraph -->\n<p>Two of those rows carry most of the value. <code>throughput-performance<\/code> is the right starting point for a general web VPS. <code>latency-performance<\/code> is the right choice when a database shares the box, because it forces dirty pages to disk far more eagerly (<code>dirty_ratio=10<\/code> instead of 40), which keeps write latency predictable at the cost of a little raw throughput. On a small NVMe VPS with a 256&nbsp;MB buffer pool, that trade is almost always correct.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Applying and verifying a profile<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile throughput-performance<br>tuned-adm active<br>tuned-adm verify<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm verify<\/code> re-reads every setting in the active profile and reports whether the live kernel state matches. If you (or a cloud-init script, or another tuning tool) changed a sysctl after tuned applied, verify will flag the drift. This is the main reason to run tuned rather than a hand-written sysctl file: it detects and can re-assert your configuration.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>To see what a profile will change before you apply it:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile_info latency-performance<br>tuned-adm recommend<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm recommend<\/code> uses the <code>recommend<\/code> plugin to inspect the host (virt type, whether it is a guest, disk type) and suggest a profile. On a KVM VPS it usually suggests <code>virtual-guest<\/code> \u2014 a safe, unambitious choice you will generally want to override.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Building a custom profile that survives upgrades<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>The practical pattern is to inherit the closest stock profile and add your own overrides. Create <code>\/etc\/tuned\/vps-db\/tuned.conf<\/code>:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>[main]<br>summary=Database-optimised profile for 2\u20134 GB VPS<br>include=latency-performance<br><br>[sysctl]<br>vm.dirty_writeback_centisecs=500<br>vm.dirty_expire_centisecs=1000<br>vm.vfs_cache_pressure=50<br>net.core.somaxconn=1024<br><br>[disk]<br>read_ahead_kb=1024<br>scheduler=none<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Then <code>tuned-adm profile vps-db<\/code>. The <code>include=<\/code> line pulls in everything from <code>latency-performance<\/code>, and your sections override or extend it. Values you do not specify are left as the parent set them.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Two cautions. First, do not set <code>vm.swappiness=0<\/code> on a small VPS: with no swap headroom you convert memory pressure directly into OOM kills instead of slow paging. <code>10<\/code> is the practical floor. Second, <code>scheduler=none<\/code> (or <code>mq-deadline<\/code>) is correct for SSDs; on spinning disk you want <code>bfq<\/code> or <code>mq-deadline<\/code> with a larger read-ahead. You can confirm the device is SSD with <code>cat \/sys\/block\/vda\/queue\/rotational<\/code> \u2014 0 means SSD.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Measuring whether it helped<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Changing a profile without measuring is guesswork. A minimal before\/after check on the database VPS:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>sysbench fileio --file-total-size=2G --file-test-mode=rndrw prepare<br>sysbench fileio --file-total-size=2G --file-test-mode=rndrw --time=60 run<br>vmstat 1 60 > \/tmp\/vmstat.after<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Watch <code>si<\/code>\/<code>so<\/code> (swap in\/out) and the <code>bo<\/code> column in <code>vmstat<\/code>. A latency profile should reduce bursty <code>bo<\/code> at the cost of slightly higher steady <code>bo<\/code>. If <code>si<\/code>\/<code>so<\/code> are non-zero in either run, your working set does not fit in RAM and no tunable profile will fix that \u2014 you need a bigger instance or a smaller buffer pool. Our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/diagnosing-vps-disk-io-latency-spikes\">diagnosing VPS disk I\/O latency spikes<\/a> covers how to tell scheduler-caused latency from genuine device saturation.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Practical recommendations<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:post-content --><!-- \/wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list-item -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list-item -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list --><!-- wp:post-content --><!-- wp:paragraph -->\n<p><code>tuned<\/code> is one of the most useful and most misunderstood packages on a Linux VPS. It ships a set of named kernel and device profiles \u2014 curated bundles of CPU governor, scheduler, virtual memory, disk queue and power settings \u2014 and applies them atomically with <code>tuned-adm profile &lt;name&gt;<\/code>. The misunderstanding is treating profiles as magic performance buttons. They are not: a profile is a bundle of conservative tunings that must match your workload or it will actively hurt you. A database VPS running a web-optimised profile will have the wrong read-ahead and dirty-ratio settings; a web VPS running a throughput profile may hold dirty pages too long and spike latency on small writes.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>This article covers the profiles that matter on a VPS, what each one actually changes, how to verify it took effect, and how to build a custom profile that layers your own sysctl values on top. If you have not yet worked through generic kernel tuning, start with our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-kernel-parameters-sysctl-tuning\">VPS kernel parameter and sysctl tuning<\/a>, since a tuned profile is just a managed overlay on the same parameters.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>What tuned actually does<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Installed via <code>apt install tuned<\/code> (Debian\/Ubuntu) or <code>dnf install tuned<\/code> (RHEL family), tuned runs as a daemon with a profile selected at <code>\/etc\/tuned\/active_profile<\/code>. On activation it performs three kinds of changes:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li><strong>sysctl<\/strong> writes \u2014 values like <code>vm.swappiness<\/code>, <code>vm.dirty_ratio<\/code>, <code>vm.dirty_background_ratio<\/code>, <code>net.ipv4.tcp_*<\/code>.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Device settings<\/strong> \u2014 I\/O scheduler on each block device (<code>\/sys\/block\/*\/queue\/scheduler<\/code>), read-ahead (<code>read_ahead_kb<\/code>), and sometimes queue depth.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Plugin actions<\/strong> \u2014 CPU governor and energy\/performance preference via the <code>cpu<\/code> plugin, disk power management, and in some profiles, transparent hugepage behaviour.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Profiles live in <code>\/usr\/lib\/tuned\/&lt;name&gt;\/tuned.conf<\/code>, with local overrides in <code>\/etc\/tuned\/<\/code>. That split matters: your custom profile in <code>\/etc\/tuned\/<\/code> survives package upgrades, edits to <code>\/usr\/lib\/<\/code> do not.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Profile comparison for VPS workloads<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Profile<\/th><th>Intended workload<\/th><th>Key vm settings<\/th><th>I\/O scheduler<\/th><th>Fit for a VPS<\/th><\/tr><\/thead><tbody><tr><td><code>throughput-performance<\/code><\/td><td>Generic server, maximum I\/O and CPU<\/td><td>swappiness=10, dirty_ratio=40, dirty_background=10<\/td><td>mq-deadline \/ none<\/td><td>Good default for web or general servers<\/td><\/tr><tr><td><code>latency-performance<\/code><\/td><td>Latency-sensitive services<\/td><td>swappiness=10, dirty_ratio=10, dirty_background=3<\/td><td>none \/ mq-deadline<\/td><td>Best for databases on SSD\/NVMe<\/td><\/tr><tr><td><code>network-latency<\/code><\/td><td>Network-focused, low per-packet latency<\/td><td>swappiness=10, aggressive THP disabled<\/td><td>unchanged<\/td><td>Useful for proxy\/firewall roles<\/td><\/tr><tr><td><code>network-throughput<\/code><\/td><td>High bandwidth bulk transfer<\/td><td>large TCP buffers<\/td><td>unchanged<\/td><td>Niche; large socket buffers cost RAM<\/td><\/tr><tr><td><code>virtual-guest<\/code><\/td><td>Generic VM guest<\/td><td>moderate swappiness=30<\/td><td>none<\/td><td>Reasonable starting point, not optimal<\/td><\/tr><tr><td><code>balanced<\/code><\/td><td>Default desktop\/server mix<\/td><td>swappiness=60, dirty_ratio=20<\/td><td>mq-deadline<\/td><td>Too conservative for a busy VPS<\/td><\/tr><tr><td><code>powersave<\/code><\/td><td>Minimise energy<\/td><td>swappiness=60+<\/td><td>unchanged<\/td><td>Never on a server<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:paragraph -->\n<p>Two of those rows carry most of the value. <code>throughput-performance<\/code> is the right starting point for a general web VPS. <code>latency-performance<\/code> is the right choice when a database shares the box, because it forces dirty pages to disk far more eagerly (<code>dirty_ratio=10<\/code> instead of 40), which keeps write latency predictable at the cost of a little raw throughput. On a small NVMe VPS with a 256&nbsp;MB buffer pool, that trade is almost always correct.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Applying and verifying a profile<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile throughput-performance<br>tuned-adm active<br>tuned-adm verify<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm verify<\/code> re-reads every setting in the active profile and reports whether the live kernel state matches. If you (or a cloud-init script, or another tuning tool) changed a sysctl after tuned applied, verify will flag the drift. This is the main reason to run tuned rather than a hand-written sysctl file: it detects and can re-assert your configuration.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>To see what a profile will change before you apply it:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile_info latency-performance<br>tuned-adm recommend<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm recommend<\/code> uses the <code>recommend<\/code> plugin to inspect the host (virt type, whether it is a guest, disk type) and suggest a profile. On a KVM VPS it usually suggests <code>virtual-guest<\/code> \u2014 a safe, unambitious choice you will generally want to override.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Building a custom profile that survives upgrades<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>The practical pattern is to inherit the closest stock profile and add your own overrides. Create <code>\/etc\/tuned\/vps-db\/tuned.conf<\/code>:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>[main]<br>summary=Database-optimised profile for 2\u20134 GB VPS<br>include=latency-performance<br><br>[sysctl]<br>vm.dirty_writeback_centisecs=500<br>vm.dirty_expire_centisecs=1000<br>vm.vfs_cache_pressure=50<br>net.core.somaxconn=1024<br><br>[disk]<br>read_ahead_kb=1024<br>scheduler=none<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Then <code>tuned-adm profile vps-db<\/code>. The <code>include=<\/code> line pulls in everything from <code>latency-performance<\/code>, and your sections override or extend it. Values you do not specify are left as the parent set them.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Two cautions. First, do not set <code>vm.swappiness=0<\/code> on a small VPS: with no swap headroom you convert memory pressure directly into OOM kills instead of slow paging. <code>10<\/code> is the practical floor. Second, <code>scheduler=none<\/code> (or <code>mq-deadline<\/code>) is correct for SSDs; on spinning disk you want <code>bfq<\/code> or <code>mq-deadline<\/code> with a larger read-ahead. You can confirm the device is SSD with <code>cat \/sys\/block\/vda\/queue\/rotational<\/code> \u2014 0 means SSD.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Measuring whether it helped<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Changing a profile without measuring is guesswork. A minimal before\/after check on the database VPS:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>sysbench fileio --file-total-size=2G --file-test-mode=rndrw prepare<br>sysbench fileio --file-total-size=2G --file-test-mode=rndrw --time=60 run<br>vmstat 1 60 > \/tmp\/vmstat.after<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Watch <code>si<\/code>\/<code>so<\/code> (swap in\/out) and the <code>bo<\/code> column in <code>vmstat<\/code>. A latency profile should reduce bursty <code>bo<\/code> at the cost of slightly higher steady <code>bo<\/code>. If <code>si<\/code>\/<code>so<\/code> are non-zero in either run, your working set does not fit in RAM and no tunable profile will fix that \u2014 you need a bigger instance or a smaller buffer pool. Our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/diagnosing-vps-disk-io-latency-spikes\">diagnosing VPS disk I\/O latency spikes<\/a> covers how to tell scheduler-caused latency from genuine device saturation.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Practical recommendations<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:post-content --><!-- \/wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list-item -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:list-item --><!-- \/wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list-item -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list-item -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list --><!-- wp:post-content --><!-- wp:paragraph -->\n<p><code>tuned<\/code> is one of the most useful and most misunderstood packages on a Linux VPS. It ships a set of named kernel and device profiles \u2014 curated bundles of CPU governor, scheduler, virtual memory, disk queue and power settings \u2014 and applies them atomically with <code>tuned-adm profile &lt;name&gt;<\/code>. The misunderstanding is treating profiles as magic performance buttons. They are not: a profile is a bundle of conservative tunings that must match your workload or it will actively hurt you. A database VPS running a web-optimised profile will have the wrong read-ahead and dirty-ratio settings; a web VPS running a throughput profile may hold dirty pages too long and spike latency on small writes.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>This article covers the profiles that matter on a VPS, what each one actually changes, how to verify it took effect, and how to build a custom profile that layers your own sysctl values on top. If you have not yet worked through generic kernel tuning, start with our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-kernel-parameters-sysctl-tuning\">VPS kernel parameter and sysctl tuning<\/a>, since a tuned profile is just a managed overlay on the same parameters.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>What tuned actually does<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Installed via <code>apt install tuned<\/code> (Debian\/Ubuntu) or <code>dnf install tuned<\/code> (RHEL family), tuned runs as a daemon with a profile selected at <code>\/etc\/tuned\/active_profile<\/code>. On activation it performs three kinds of changes:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li><strong>sysctl<\/strong> writes \u2014 values like <code>vm.swappiness<\/code>, <code>vm.dirty_ratio<\/code>, <code>vm.dirty_background_ratio<\/code>, <code>net.ipv4.tcp_*<\/code>.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Device settings<\/strong> \u2014 I\/O scheduler on each block device (<code>\/sys\/block\/*\/queue\/scheduler<\/code>), read-ahead (<code>read_ahead_kb<\/code>), and sometimes queue depth.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Plugin actions<\/strong> \u2014 CPU governor and energy\/performance preference via the <code>cpu<\/code> plugin, disk power management, and in some profiles, transparent hugepage behaviour.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Profiles live in <code>\/usr\/lib\/tuned\/&lt;name&gt;\/tuned.conf<\/code>, with local overrides in <code>\/etc\/tuned\/<\/code>. That split matters: your custom profile in <code>\/etc\/tuned\/<\/code> survives package upgrades, edits to <code>\/usr\/lib\/<\/code> do not.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Profile comparison for VPS workloads<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Profile<\/th><th>Intended workload<\/th><th>Key vm settings<\/th><th>I\/O scheduler<\/th><th>Fit for a VPS<\/th><\/tr><\/thead><tbody><tr><td><code>throughput-performance<\/code><\/td><td>Generic server, maximum I\/O and CPU<\/td><td>swappiness=10, dirty_ratio=40, dirty_background=10<\/td><td>mq-deadline \/ none<\/td><td>Good default for web or general servers<\/td><\/tr><tr><td><code>latency-performance<\/code><\/td><td>Latency-sensitive services<\/td><td>swappiness=10, dirty_ratio=10, dirty_background=3<\/td><td>none \/ mq-deadline<\/td><td>Best for databases on SSD\/NVMe<\/td><\/tr><tr><td><code>network-latency<\/code><\/td><td>Network-focused, low per-packet latency<\/td><td>swappiness=10, aggressive THP disabled<\/td><td>unchanged<\/td><td>Useful for proxy\/firewall roles<\/td><\/tr><tr><td><code>network-throughput<\/code><\/td><td>High bandwidth bulk transfer<\/td><td>large TCP buffers<\/td><td>unchanged<\/td><td>Niche; large socket buffers cost RAM<\/td><\/tr><tr><td><code>virtual-guest<\/code><\/td><td>Generic VM guest<\/td><td>moderate swappiness=30<\/td><td>none<\/td><td>Reasonable starting point, not optimal<\/td><\/tr><tr><td><code>balanced<\/code><\/td><td>Default desktop\/server mix<\/td><td>swappiness=60, dirty_ratio=20<\/td><td>mq-deadline<\/td><td>Too conservative for a busy VPS<\/td><\/tr><tr><td><code>powersave<\/code><\/td><td>Minimise energy<\/td><td>swappiness=60+<\/td><td>unchanged<\/td><td>Never on a server<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:paragraph -->\n<p>Two of those rows carry most of the value. <code>throughput-performance<\/code> is the right starting point for a general web VPS. <code>latency-performance<\/code> is the right choice when a database shares the box, because it forces dirty pages to disk far more eagerly (<code>dirty_ratio=10<\/code> instead of 40), which keeps write latency predictable at the cost of a little raw throughput. On a small NVMe VPS with a 256&nbsp;MB buffer pool, that trade is almost always correct.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Applying and verifying a profile<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile throughput-performance<br>tuned-adm active<br>tuned-adm verify<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm verify<\/code> re-reads every setting in the active profile and reports whether the live kernel state matches. If you (or a cloud-init script, or another tuning tool) changed a sysctl after tuned applied, verify will flag the drift. This is the main reason to run tuned rather than a hand-written sysctl file: it detects and can re-assert your configuration.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>To see what a profile will change before you apply it:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile_info latency-performance<br>tuned-adm recommend<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm recommend<\/code> uses the <code>recommend<\/code> plugin to inspect the host (virt type, whether it is a guest, disk type) and suggest a profile. On a KVM VPS it usually suggests <code>virtual-guest<\/code> \u2014 a safe, unambitious choice you will generally want to override.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Building a custom profile that survives upgrades<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>The practical pattern is to inherit the closest stock profile and add your own overrides. Create <code>\/etc\/tuned\/vps-db\/tuned.conf<\/code>:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>[main]<br>summary=Database-optimised profile for 2\u20134 GB VPS<br>include=latency-performance<br><br>[sysctl]<br>vm.dirty_writeback_centisecs=500<br>vm.dirty_expire_centisecs=1000<br>vm.vfs_cache_pressure=50<br>net.core.somaxconn=1024<br><br>[disk]<br>read_ahead_kb=1024<br>scheduler=none<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Then <code>tuned-adm profile vps-db<\/code>. The <code>include=<\/code> line pulls in everything from <code>latency-performance<\/code>, and your sections override or extend it. Values you do not specify are left as the parent set them.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Two cautions. First, do not set <code>vm.swappiness=0<\/code> on a small VPS: with no swap headroom you convert memory pressure directly into OOM kills instead of slow paging. <code>10<\/code> is the practical floor. Second, <code>scheduler=none<\/code> (or <code>mq-deadline<\/code>) is correct for SSDs; on spinning disk you want <code>bfq<\/code> or <code>mq-deadline<\/code> with a larger read-ahead. You can confirm the device is SSD with <code>cat \/sys\/block\/vda\/queue\/rotational<\/code> \u2014 0 means SSD.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Measuring whether it helped<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Changing a profile without measuring is guesswork. A minimal before\/after check on the database VPS:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>sysbench fileio --file-total-size=2G --file-test-mode=rndrw prepare<br>sysbench fileio --file-total-size=2G --file-test-mode=rndrw --time=60 run<br>vmstat 1 60 > \/tmp\/vmstat.after<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Watch <code>si<\/code>\/<code>so<\/code> (swap in\/out) and the <code>bo<\/code> column in <code>vmstat<\/code>. A latency profile should reduce bursty <code>bo<\/code> at the cost of slightly higher steady <code>bo<\/code>. If <code>si<\/code>\/<code>so<\/code> are non-zero in either run, your working set does not fit in RAM and no tunable profile will fix that \u2014 you need a bigger instance or a smaller buffer pool. Our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/diagnosing-vps-disk-io-latency-spikes\">diagnosing VPS disk I\/O latency spikes<\/a> covers how to tell scheduler-caused latency from genuine device saturation.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Practical recommendations<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:post-content --><!-- \/wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list-item -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list-item -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list --><!-- wp:post-content --><!-- wp:paragraph -->\n<p><code>tuned<\/code> is one of the most useful and most misunderstood packages on a Linux VPS. It ships a set of named kernel and device profiles \u2014 curated bundles of CPU governor, scheduler, virtual memory, disk queue and power settings \u2014 and applies them atomically with <code>tuned-adm profile &lt;name&gt;<\/code>. The misunderstanding is treating profiles as magic performance buttons. They are not: a profile is a bundle of conservative tunings that must match your workload or it will actively hurt you. A database VPS running a web-optimised profile will have the wrong read-ahead and dirty-ratio settings; a web VPS running a throughput profile may hold dirty pages too long and spike latency on small writes.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>This article covers the profiles that matter on a VPS, what each one actually changes, how to verify it took effect, and how to build a custom profile that layers your own sysctl values on top. If you have not yet worked through generic kernel tuning, start with our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-kernel-parameters-sysctl-tuning\">VPS kernel parameter and sysctl tuning<\/a>, since a tuned profile is just a managed overlay on the same parameters.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>What tuned actually does<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Installed via <code>apt install tuned<\/code> (Debian\/Ubuntu) or <code>dnf install tuned<\/code> (RHEL family), tuned runs as a daemon with a profile selected at <code>\/etc\/tuned\/active_profile<\/code>. On activation it performs three kinds of changes:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li><strong>sysctl<\/strong> writes \u2014 values like <code>vm.swappiness<\/code>, <code>vm.dirty_ratio<\/code>, <code>vm.dirty_background_ratio<\/code>, <code>net.ipv4.tcp_*<\/code>.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Device settings<\/strong> \u2014 I\/O scheduler on each block device (<code>\/sys\/block\/*\/queue\/scheduler<\/code>), read-ahead (<code>read_ahead_kb<\/code>), and sometimes queue depth.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Plugin actions<\/strong> \u2014 CPU governor and energy\/performance preference via the <code>cpu<\/code> plugin, disk power management, and in some profiles, transparent hugepage behaviour.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Profiles live in <code>\/usr\/lib\/tuned\/&lt;name&gt;\/tuned.conf<\/code>, with local overrides in <code>\/etc\/tuned\/<\/code>. That split matters: your custom profile in <code>\/etc\/tuned\/<\/code> survives package upgrades, edits to <code>\/usr\/lib\/<\/code> do not.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Profile comparison for VPS workloads<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Profile<\/th><th>Intended workload<\/th><th>Key vm settings<\/th><th>I\/O scheduler<\/th><th>Fit for a VPS<\/th><\/tr><\/thead><tbody><tr><td><code>throughput-performance<\/code><\/td><td>Generic server, maximum I\/O and CPU<\/td><td>swappiness=10, dirty_ratio=40, dirty_background=10<\/td><td>mq-deadline \/ none<\/td><td>Good default for web or general servers<\/td><\/tr><tr><td><code>latency-performance<\/code><\/td><td>Latency-sensitive services<\/td><td>swappiness=10, dirty_ratio=10, dirty_background=3<\/td><td>none \/ mq-deadline<\/td><td>Best for databases on SSD\/NVMe<\/td><\/tr><tr><td><code>network-latency<\/code><\/td><td>Network-focused, low per-packet latency<\/td><td>swappiness=10, aggressive THP disabled<\/td><td>unchanged<\/td><td>Useful for proxy\/firewall roles<\/td><\/tr><tr><td><code>network-throughput<\/code><\/td><td>High bandwidth bulk transfer<\/td><td>large TCP buffers<\/td><td>unchanged<\/td><td>Niche; large socket buffers cost RAM<\/td><\/tr><tr><td><code>virtual-guest<\/code><\/td><td>Generic VM guest<\/td><td>moderate swappiness=30<\/td><td>none<\/td><td>Reasonable starting point, not optimal<\/td><\/tr><tr><td><code>balanced<\/code><\/td><td>Default desktop\/server mix<\/td><td>swappiness=60, dirty_ratio=20<\/td><td>mq-deadline<\/td><td>Too conservative for a busy VPS<\/td><\/tr><tr><td><code>powersave<\/code><\/td><td>Minimise energy<\/td><td>swappiness=60+<\/td><td>unchanged<\/td><td>Never on a server<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:paragraph -->\n<p>Two of those rows carry most of the value. <code>throughput-performance<\/code> is the right starting point for a general web VPS. <code>latency-performance<\/code> is the right choice when a database shares the box, because it forces dirty pages to disk far more eagerly (<code>dirty_ratio=10<\/code> instead of 40), which keeps write latency predictable at the cost of a little raw throughput. On a small NVMe VPS with a 256&nbsp;MB buffer pool, that trade is almost always correct.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Applying and verifying a profile<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile throughput-performance<br>tuned-adm active<br>tuned-adm verify<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm verify<\/code> re-reads every setting in the active profile and reports whether the live kernel state matches. If you (or a cloud-init script, or another tuning tool) changed a sysctl after tuned applied, verify will flag the drift. This is the main reason to run tuned rather than a hand-written sysctl file: it detects and can re-assert your configuration.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>To see what a profile will change before you apply it:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile_info latency-performance<br>tuned-adm recommend<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm recommend<\/code> uses the <code>recommend<\/code> plugin to inspect the host (virt type, whether it is a guest, disk type) and suggest a profile. On a KVM VPS it usually suggests <code>virtual-guest<\/code> \u2014 a safe, unambitious choice you will generally want to override.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Building a custom profile that survives upgrades<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>The practical pattern is to inherit the closest stock profile and add your own overrides. Create <code>\/etc\/tuned\/vps-db\/tuned.conf<\/code>:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>[main]<br>summary=Database-optimised profile for 2\u20134 GB VPS<br>include=latency-performance<br><br>[sysctl]<br>vm.dirty_writeback_centisecs=500<br>vm.dirty_expire_centisecs=1000<br>vm.vfs_cache_pressure=50<br>net.core.somaxconn=1024<br><br>[disk]<br>read_ahead_kb=1024<br>scheduler=none<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Then <code>tuned-adm profile vps-db<\/code>. The <code>include=<\/code> line pulls in everything from <code>latency-performance<\/code>, and your sections override or extend it. Values you do not specify are left as the parent set them.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Two cautions. First, do not set <code>vm.swappiness=0<\/code> on a small VPS: with no swap headroom you convert memory pressure directly into OOM kills instead of slow paging. <code>10<\/code> is the practical floor. Second, <code>scheduler=none<\/code> (or <code>mq-deadline<\/code>) is correct for SSDs; on spinning disk you want <code>bfq<\/code> or <code>mq-deadline<\/code> with a larger read-ahead. You can confirm the device is SSD with <code>cat \/sys\/block\/vda\/queue\/rotational<\/code> \u2014 0 means SSD.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Measuring whether it helped<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Changing a profile without measuring is guesswork. A minimal before\/after check on the database VPS:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>sysbench fileio --file-total-size=2G --file-test-mode=rndrw prepare<br>sysbench fileio --file-total-size=2G --file-test-mode=rndrw --time=60 run<br>vmstat 1 60 > \/tmp\/vmstat.after<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Watch <code>si<\/code>\/<code>so<\/code> (swap in\/out) and the <code>bo<\/code> column in <code>vmstat<\/code>. A latency profile should reduce bursty <code>bo<\/code> at the cost of slightly higher steady <code>bo<\/code>. If <code>si<\/code>\/<code>so<\/code> are non-zero in either run, your working set does not fit in RAM and no tunable profile will fix that \u2014 you need a bigger instance or a smaller buffer pool. Our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/diagnosing-vps-disk-io-latency-spikes\">diagnosing VPS disk I\/O latency spikes<\/a> covers how to tell scheduler-caused latency from genuine device saturation.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Practical recommendations<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:post-content --><!-- \/wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list-item -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:list-item --><!-- \/wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list-item -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list-item -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:list --><!-- wp:post-content --><!-- wp:paragraph -->\n<p><code>tuned<\/code> is one of the most useful and most misunderstood packages on a Linux VPS. It ships a set of named kernel and device profiles \u2014 curated bundles of CPU governor, scheduler, virtual memory, disk queue and power settings \u2014 and applies them atomically with <code>tuned-adm profile &lt;name&gt;<\/code>. The misunderstanding is treating profiles as magic performance buttons. They are not: a profile is a bundle of conservative tunings that must match your workload or it will actively hurt you. A database VPS running a web-optimised profile will have the wrong read-ahead and dirty-ratio settings; a web VPS running a throughput profile may hold dirty pages too long and spike latency on small writes.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>This article covers the profiles that matter on a VPS, what each one actually changes, how to verify it took effect, and how to build a custom profile that layers your own sysctl values on top. If you have not yet worked through generic kernel tuning, start with our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-kernel-parameters-sysctl-tuning\">VPS kernel parameter and sysctl tuning<\/a>, since a tuned profile is just a managed overlay on the same parameters.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>What tuned actually does<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Installed via <code>apt install tuned<\/code> (Debian\/Ubuntu) or <code>dnf install tuned<\/code> (RHEL family), tuned runs as a daemon with a profile selected at <code>\/etc\/tuned\/active_profile<\/code>. On activation it performs three kinds of changes:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li><strong>sysctl<\/strong> writes \u2014 values like <code>vm.swappiness<\/code>, <code>vm.dirty_ratio<\/code>, <code>vm.dirty_background_ratio<\/code>, <code>net.ipv4.tcp_*<\/code>.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Device settings<\/strong> \u2014 I\/O scheduler on each block device (<code>\/sys\/block\/*\/queue\/scheduler<\/code>), read-ahead (<code>read_ahead_kb<\/code>), and sometimes queue depth.<\/li><!-- \/wp:list-item --><!-- wp:list-item --><li><strong>Plugin actions<\/strong> \u2014 CPU governor and energy\/performance preference via the <code>cpu<\/code> plugin, disk power management, and in some profiles, transparent hugepage behaviour.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Profiles live in <code>\/usr\/lib\/tuned\/&lt;name&gt;\/tuned.conf<\/code>, with local overrides in <code>\/etc\/tuned\/<\/code>. That split matters: your custom profile in <code>\/etc\/tuned\/<\/code> survives package upgrades, edits to <code>\/usr\/lib\/<\/code> do not.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Profile comparison for VPS workloads<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:table -->\n<figure class=\"wp-block-table\"><table><thead><tr><th>Profile<\/th><th>Intended workload<\/th><th>Key vm settings<\/th><th>I\/O scheduler<\/th><th>Fit for a VPS<\/th><\/tr><\/thead><tbody><tr><td><code>throughput-performance<\/code><\/td><td>Generic server, maximum I\/O and CPU<\/td><td>swappiness=10, dirty_ratio=40, dirty_background=10<\/td><td>mq-deadline \/ none<\/td><td>Good default for web or general servers<\/td><\/tr><tr><td><code>latency-performance<\/code><\/td><td>Latency-sensitive services<\/td><td>swappiness=10, dirty_ratio=10, dirty_background=3<\/td><td>none \/ mq-deadline<\/td><td>Best for databases on SSD\/NVMe<\/td><\/tr><tr><td><code>network-latency<\/code><\/td><td>Network-focused, low per-packet latency<\/td><td>swappiness=10, aggressive THP disabled<\/td><td>unchanged<\/td><td>Useful for proxy\/firewall roles<\/td><\/tr><tr><td><code>network-throughput<\/code><\/td><td>High bandwidth bulk transfer<\/td><td>large TCP buffers<\/td><td>unchanged<\/td><td>Niche; large socket buffers cost RAM<\/td><\/tr><tr><td><code>virtual-guest<\/code><\/td><td>Generic VM guest<\/td><td>moderate swappiness=30<\/td><td>none<\/td><td>Reasonable starting point, not optimal<\/td><\/tr><tr><td><code>balanced<\/code><\/td><td>Default desktop\/server mix<\/td><td>swappiness=60, dirty_ratio=20<\/td><td>mq-deadline<\/td><td>Too conservative for a busy VPS<\/td><\/tr><tr><td><code>powersave<\/code><\/td><td>Minimise energy<\/td><td>swappiness=60+<\/td><td>unchanged<\/td><td>Never on a server<\/td><\/tr><\/tbody><\/table><\/figure>\n<!-- \/wp:table -->\n\n<!-- wp:paragraph -->\n<p>Two of those rows carry most of the value. <code>throughput-performance<\/code> is the right starting point for a general web VPS. <code>latency-performance<\/code> is the right choice when a database shares the box, because it forces dirty pages to disk far more eagerly (<code>dirty_ratio=10<\/code> instead of 40), which keeps write latency predictable at the cost of a little raw throughput. On a small NVMe VPS with a 256&nbsp;MB buffer pool, that trade is almost always correct.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Applying and verifying a profile<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile throughput-performance<br>tuned-adm active<br>tuned-adm verify<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm verify<\/code> re-reads every setting in the active profile and reports whether the live kernel state matches. If you (or a cloud-init script, or another tuning tool) changed a sysctl after tuned applied, verify will flag the drift. This is the main reason to run tuned rather than a hand-written sysctl file: it detects and can re-assert your configuration.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>To see what a profile will change before you apply it:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm profile_info latency-performance<br>tuned-adm recommend<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>tuned-adm recommend<\/code> uses the <code>recommend<\/code> plugin to inspect the host (virt type, whether it is a guest, disk type) and suggest a profile. On a KVM VPS it usually suggests <code>virtual-guest<\/code> \u2014 a safe, unambitious choice you will generally want to override.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Building a custom profile that survives upgrades<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>The practical pattern is to inherit the closest stock profile and add your own overrides. Create <code>\/etc\/tuned\/vps-db\/tuned.conf<\/code>:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>[main]<br>summary=Database-optimised profile for 2\u20134 GB VPS<br>include=latency-performance<br><br>[sysctl]<br>vm.dirty_writeback_centisecs=500<br>vm.dirty_expire_centisecs=1000<br>vm.vfs_cache_pressure=50<br>net.core.somaxconn=1024<br><br>[disk]<br>read_ahead_kb=1024<br>scheduler=none<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Then <code>tuned-adm profile vps-db<\/code>. The <code>include=<\/code> line pulls in everything from <code>latency-performance<\/code>, and your sections override or extend it. Values you do not specify are left as the parent set them.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Two cautions. First, do not set <code>vm.swappiness=0<\/code> on a small VPS: with no swap headroom you convert memory pressure directly into OOM kills instead of slow paging. <code>10<\/code> is the practical floor. Second, <code>scheduler=none<\/code> (or <code>mq-deadline<\/code>) is correct for SSDs; on spinning disk you want <code>bfq<\/code> or <code>mq-deadline<\/code> with a larger read-ahead. You can confirm the device is SSD with <code>cat \/sys\/block\/vda\/queue\/rotational<\/code> \u2014 0 means SSD.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Measuring whether it helped<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:paragraph -->\n<p>Changing a profile without measuring is guesswork. A minimal before\/after check on the database VPS:<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p><code>sysbench fileio --file-total-size=2G --file-test-mode=rndrw prepare<br>sysbench fileio --file-total-size=2G --file-test-mode=rndrw --time=60 run<br>vmstat 1 60 > \/tmp\/vmstat.after<\/code><\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:paragraph -->\n<p>Watch <code>si<\/code>\/<code>so<\/code> (swap in\/out) and the <code>bo<\/code> column in <code>vmstat<\/code>. A latency profile should reduce bursty <code>bo<\/code> at the cost of slightly higher steady <code>bo<\/code>. If <code>si<\/code>\/<code>so<\/code> are non-zero in either run, your working set does not fit in RAM and no tunable profile will fix that \u2014 you need a bigger instance or a smaller buffer pool. Our guide to <a href=\"https:\/\/virtualserversvps.com\/blog\/diagnosing-vps-disk-io-latency-spikes\">diagnosing VPS disk I\/O latency spikes<\/a> covers how to tell scheduler-caused latency from genuine device saturation.<\/p>\n<!-- \/wp:paragraph -->\n\n<!-- wp:heading {\"level\":2} -->\n<h2>Practical recommendations<\/h2>\n<!-- \/wp:heading -->\n\n<!-- wp:list -->\n<ul><!-- wp:list-item --><li>General web VPS: <code>throughput-performance<\/code>.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>VPS running MySQL\/MariaDB\/PostgreSQL: <code>latency-performance<\/code>, or a custom profile derived from it.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Tiny 512&nbsp;MB VPS: <code>latency-performance<\/code> plus <code>vm.swappiness=10<\/code> and a small swap file \u2014 not zero swap.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Never leave the daemon on <code>balanced<\/code> if you chose tuned for performance; that is the distro default and you gain nothing.<\/li><!-- wp:list-item --><!-- wp:list-item --><li>Always run <code>tuned-adm verify<\/code> after provisioning and after any cloud-init run.<\/li><!-- \/wp:list-item --><\/ul>\n<!-- \/wp:list -->\n\n<!-- wp:paragraph -->\n<p>Finally, remember that tuned does not size your applications. It cannot set an InnoDB buffer pool, an OPcache limit, or a PHP-FPM pool ceiling. Those decisions dominate performance on a small VPS long before kernel tunables do. Treat tuned as the last 5% of tuning, applied after the application layer is correctly sized \u2014 not as the first thing you reach for. For a full picture of what to size first, see our <a href=\"https:\/\/virtualserversvps.com\/blog\/vps-nginx-performance-tuning-caching\">Nginx and caching tuning walkthrough<\/a>.<\/p>\n<!-- \/wp:paragraph -->\n<!-- \/wp:post-content --><!-- \/wp:post-content -->","protected":false},"excerpt":{"rendered":"<p>Tiny 512&nbsp;MB VPS: latency-performance plus vm.swappiness=10 and a small swap file \u2014 not zero swap. Never leave the daemon on balanced if you chose tuned for performance; that is the&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"iawp_total_views":4,"footnotes":""},"categories":[3],"tags":[],"class_list":["post-1046","post","type-post","status-publish","format-standard","hentry","category-performance-optimization"],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v26.1 (Yoast SEO v26.1) - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>tuned-adm on a VPS: Matching Kernel Profiles to Web, Database and Latency-Sensitive Workloads - Virtual Servers VPS Blog<\/title>\n<meta name=\"description\" content=\"Never leave the daemon on balanced if you chose tuned for performance; that is the distro default and you gain nothing.Always run tuned-adm verify after\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/virtualserversvps.com\/blog\/tuned-adm-vps-performance-profiles\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"tuned-adm on a VPS: Matching Kernel Profiles to Web, Database and Latency-Sensitive Workloads\" \/>\n<meta property=\"og:description\" content=\"tuned-adm on a VPS: Matching Kernel Profiles to Web, Database and Latency-Sensitive Workloads\" \/>\n<meta property=\"og:url\" content=\"https:\/\/virtualserversvps.com\/blog\/tuned-adm-vps-performance-profiles\/\" \/>\n<meta property=\"og:site_name\" content=\"Virtual Servers VPS Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-09-03T23:12:14+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-09-20T22:02:57+00:00\" \/>\n<meta name=\"author\" content=\"Virtual-Servers-Vps-Editor\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Virtual-Servers-Vps-Editor\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"10 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/tuned-adm-vps-performance-profiles\/\",\"url\":\"https:\/\/virtualserversvps.com\/blog\/tuned-adm-vps-performance-profiles\/\",\"name\":\"tuned-adm on a VPS: Matching Kernel Profiles to Web, Database and Latency-Sensitive Workloads - Virtual Servers VPS Blog\",\"isPartOf\":{\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#website\"},\"datePublished\":\"2026-09-03T23:12:14+00:00\",\"dateModified\":\"2026-09-20T22:02:57+00:00\",\"author\":{\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/82a299a8284a66ff49f97c74684724a0\"},\"description\":\"Never leave the daemon on balanced if you chose tuned for performance; that is the distro default and you gain nothing.Always run tuned-adm verify after\",\"breadcrumb\":{\"@id\":\"https:\/\/virtualserversvps.com\/blog\/tuned-adm-vps-performance-profiles\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/virtualserversvps.com\/blog\/tuned-adm-vps-performance-profiles\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/tuned-adm-vps-performance-profiles\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/virtualserversvps.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"tuned-adm on a VPS: Matching Kernel Profiles to Web, Database and Latency-Sensitive Workloads\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#website\",\"url\":\"https:\/\/virtualserversvps.com\/blog\/\",\"name\":\"Virtual Servers VPS Blog\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/virtualserversvps.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/82a299a8284a66ff49f97c74684724a0\",\"name\":\"Virtual-Servers-Vps-Editor\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/d820b15f1cd028e97610d9adf536df7be5cb6423869967037d468d5355fa003f?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/d820b15f1cd028e97610d9adf536df7be5cb6423869967037d468d5355fa003f?s=96&d=mm&r=g\",\"caption\":\"Virtual-Servers-Vps-Editor\"},\"sameAs\":[\"https:\/\/virtualserversvps.com\/blog\"],\"url\":\"https:\/\/virtualserversvps.com\/blog\/author\/virtualserversvps\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"tuned-adm on a VPS: Matching Kernel Profiles to Web, Database and Latency-Sensitive Workloads - Virtual Servers VPS Blog","description":"Never leave the daemon on balanced if you chose tuned for performance; that is the distro default and you gain nothing.Always run tuned-adm verify after","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/virtualserversvps.com\/blog\/tuned-adm-vps-performance-profiles\/","og_locale":"en_US","og_type":"article","og_title":"tuned-adm on a VPS: Matching Kernel Profiles to Web, Database and Latency-Sensitive Workloads","og_description":"tuned-adm on a VPS: Matching Kernel Profiles to Web, Database and Latency-Sensitive Workloads","og_url":"https:\/\/virtualserversvps.com\/blog\/tuned-adm-vps-performance-profiles\/","og_site_name":"Virtual Servers VPS Blog","article_published_time":"2026-09-03T23:12:14+00:00","article_modified_time":"2026-09-20T22:02:57+00:00","author":"Virtual-Servers-Vps-Editor","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Virtual-Servers-Vps-Editor","Est. reading time":"10 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/virtualserversvps.com\/blog\/tuned-adm-vps-performance-profiles\/","url":"https:\/\/virtualserversvps.com\/blog\/tuned-adm-vps-performance-profiles\/","name":"tuned-adm on a VPS: Matching Kernel Profiles to Web, Database and Latency-Sensitive Workloads - Virtual Servers VPS Blog","isPartOf":{"@id":"https:\/\/virtualserversvps.com\/blog\/#website"},"datePublished":"2026-09-03T23:12:14+00:00","dateModified":"2026-09-20T22:02:57+00:00","author":{"@id":"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/82a299a8284a66ff49f97c74684724a0"},"description":"Never leave the daemon on balanced if you chose tuned for performance; that is the distro default and you gain nothing.Always run tuned-adm verify after","breadcrumb":{"@id":"https:\/\/virtualserversvps.com\/blog\/tuned-adm-vps-performance-profiles\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/virtualserversvps.com\/blog\/tuned-adm-vps-performance-profiles\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/virtualserversvps.com\/blog\/tuned-adm-vps-performance-profiles\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/virtualserversvps.com\/blog\/"},{"@type":"ListItem","position":2,"name":"tuned-adm on a VPS: Matching Kernel Profiles to Web, Database and Latency-Sensitive Workloads"}]},{"@type":"WebSite","@id":"https:\/\/virtualserversvps.com\/blog\/#website","url":"https:\/\/virtualserversvps.com\/blog\/","name":"Virtual Servers VPS Blog","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/virtualserversvps.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/82a299a8284a66ff49f97c74684724a0","name":"Virtual-Servers-Vps-Editor","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/virtualserversvps.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/d820b15f1cd028e97610d9adf536df7be5cb6423869967037d468d5355fa003f?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/d820b15f1cd028e97610d9adf536df7be5cb6423869967037d468d5355fa003f?s=96&d=mm&r=g","caption":"Virtual-Servers-Vps-Editor"},"sameAs":["https:\/\/virtualserversvps.com\/blog"],"url":"https:\/\/virtualserversvps.com\/blog\/author\/virtualserversvps\/"}]}},"_links":{"self":[{"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/posts\/1046","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/comments?post=1046"}],"version-history":[{"count":2,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/posts\/1046\/revisions"}],"predecessor-version":[{"id":1186,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/posts\/1046\/revisions\/1186"}],"wp:attachment":[{"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/media?parent=1046"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/categories?post=1046"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/virtualserversvps.com\/blog\/wp-json\/wp\/v2\/tags?post=1046"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}