<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: VPSPioneer</title>
    <description>The latest articles on DEV Community by VPSPioneer (@vpspioneer).</description>
    <link>https://dev.to/vpspioneer</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4166915%2Fa5670ecf-fc01-4b40-8dfe-53ea4143d1cf.png</url>
      <title>DEV Community: VPSPioneer</title>
      <link>https://dev.to/vpspioneer</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vpspioneer"/>
    <language>en</language>
    <item>
      <title>How to Move a Website to a New Host Without Downtime</title>
      <dc:creator>VPSPioneer</dc:creator>
      <pubDate>Sat, 10 Oct 2026 08:57:59 +0000</pubDate>
      <link>https://dev.to/vpspioneer/how-to-move-a-website-to-a-new-host-without-downtime-45ko</link>
      <guid>https://dev.to/vpspioneer/how-to-move-a-website-to-a-new-host-without-downtime-45ko</guid>
      <description>&lt;p&gt;Most migrations go wrong at one of two points: the site is copied while it is still changing, or DNS is switched before anyone has checked the copy. This sequence avoids both. It works for any PHP site — WordPress, Laravel, a plain HTML site — and takes an hour for a typical site, most of it waiting.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Lower the DNS TTL a day ahead
&lt;/h2&gt;

&lt;p&gt;The TTL tells resolvers how long to remember your old IP. If it is 24 hours, some visitors keep hitting the old server for a day after you switch. Find the current value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dig example.com A +noall +answer
&lt;span class="c"&gt;# example.com.  86400  IN  A  203.0.113.10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;86400&lt;/code&gt; is the TTL in seconds. Set it to &lt;code&gt;300&lt;/code&gt; at your DNS provider now, and wait at least the &lt;em&gt;old&lt;/em&gt; TTL before doing the switch. This step is why the migration starts a day before it happens.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Copy the files
&lt;/h2&gt;

&lt;p&gt;From the new server, pull the files over SSH. &lt;code&gt;rsync&lt;/code&gt; only transfers what changed, so you can run it again later to catch anything modified in between:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rsync &lt;span class="nt"&gt;-avz&lt;/span&gt; &lt;span class="nt"&gt;--progress&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--exclude&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'wp-content/cache/'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--exclude&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'*.log'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  user@old-server:/var/www/example.com/ &lt;span class="se"&gt;\&lt;/span&gt;
  /var/www/vhosts/example.com/httpdocs/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No SSH on the old host? Download a backup from its control panel and unpack it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /var/www/vhosts/example.com/httpdocs/
&lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xzf&lt;/span&gt; ~/backup-example.com.tar.gz &lt;span class="nt"&gt;--strip-components&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fix ownership afterwards so PHP can write to uploads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; example_user:psacln /var/www/vhosts/example.com/httpdocs/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Copy the database
&lt;/h2&gt;

&lt;p&gt;Dump on the old server, import on the new one. &lt;code&gt;--single-transaction&lt;/code&gt; takes a consistent snapshot without locking tables, so the live site keeps working during the dump:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# on the old server&lt;/span&gt;
mysqldump &lt;span class="nt"&gt;--single-transaction&lt;/span&gt; &lt;span class="nt"&gt;--quick&lt;/span&gt; &lt;span class="nt"&gt;--routines&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-u&lt;/span&gt; dbuser &lt;span class="nt"&gt;-p&lt;/span&gt; example_db | &lt;span class="nb"&gt;gzip&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; example_db.sql.gz

&lt;span class="c"&gt;# on the new server&lt;/span&gt;
&lt;span class="nb"&gt;gunzip&lt;/span&gt; &amp;lt; example_db.sql.gz | mysql &lt;span class="nt"&gt;-u&lt;/span&gt; newuser &lt;span class="nt"&gt;-p&lt;/span&gt; new_example_db
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then update the site's config with the new credentials — for WordPress that is &lt;code&gt;wp-config.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DB_NAME'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'new_example_db'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DB_USER'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'newuser'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DB_PASSWORD'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'the-new-password'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'DB_HOST'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'localhost'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Test the copy before touching DNS
&lt;/h2&gt;

&lt;p&gt;This is the step people skip. Tell &lt;em&gt;your own computer&lt;/em&gt; that &lt;code&gt;example.com&lt;/code&gt; lives at the new IP, without changing DNS for anyone else. Edit the hosts file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# macOS / Linux&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/hosts

&lt;span class="c"&gt;# Windows (as Administrator)&lt;/span&gt;
notepad C:&lt;span class="se"&gt;\W&lt;/span&gt;indows&lt;span class="se"&gt;\S&lt;/span&gt;ystem32&lt;span class="se"&gt;\d&lt;/span&gt;rivers&lt;span class="se"&gt;\e&lt;/span&gt;tc&lt;span class="se"&gt;\h&lt;/span&gt;osts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add a line with the new server's IP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;203.0.113.55  example.com www.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now open the site in a browser. You are looking at the new server; everyone else still sees the old one. Click through the pages, log in to the admin, upload a file, place a test order. If SSL complains, that is expected — the certificate is issued in step 5.&lt;/p&gt;

&lt;p&gt;Remove the hosts line when you are done testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Issue the certificate on the new server
&lt;/h2&gt;

&lt;p&gt;Let's Encrypt needs DNS to point at the new server to validate over HTTP — which it does not yet. Two ways round it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DNS validation&lt;/strong&gt; (wildcard flow in Plesk) works before the switch.&lt;/li&gt;
&lt;li&gt;Or issue it immediately &lt;em&gt;after&lt;/em&gt; the switch; with a 300-second TTL, that is a two-minute window of browser warnings.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We use the first. See &lt;a href="https://vpspioneer.com/blog/plesk/install-free-ssl-certificate-plesk" rel="noopener noreferrer"&gt;Install a free SSL certificate in Plesk&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Freeze, re-sync, switch
&lt;/h2&gt;

&lt;p&gt;Right before the switch, put the old site into maintenance mode or just avoid publishing for ten minutes. Run the file and database copy again so nothing written in the meantime is lost:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rsync &lt;span class="nt"&gt;-avz&lt;/span&gt; user@old-server:/var/www/example.com/ /var/www/vhosts/example.com/httpdocs/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then change the &lt;code&gt;A&lt;/code&gt; record (and &lt;code&gt;www&lt;/code&gt;) to the new IP. With the TTL at 300, the world follows within five minutes. Watch it happen:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;watch &lt;span class="nt"&gt;-n&lt;/span&gt; 10 &lt;span class="s1"&gt;'dig +short example.com A @1.1.1.1'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  7. Move email and the small things
&lt;/h2&gt;

&lt;p&gt;Email is what people forget. If mailboxes live on the old host, create them on the new server first, then move the messages with &lt;code&gt;imapsync&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;imapsync &lt;span class="nt"&gt;--host1&lt;/span&gt; old.example.com &lt;span class="nt"&gt;--user1&lt;/span&gt; you@example.com &lt;span class="nt"&gt;--password1&lt;/span&gt; &lt;span class="s1"&gt;'old-pass'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
         &lt;span class="nt"&gt;--host2&lt;/span&gt; mail.example.com &lt;span class="nt"&gt;--user2&lt;/span&gt; you@example.com &lt;span class="nt"&gt;--password2&lt;/span&gt; &lt;span class="s1"&gt;'new-pass'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also check: cron jobs, &lt;code&gt;.htaccess&lt;/code&gt; redirects, any API keys tied to the old IP, and the SPF record — it must list the new mail server or your mail lands in spam.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the old server for a week
&lt;/h2&gt;

&lt;p&gt;Do not cancel the old hosting the same day. Leave it running for a week so a forgotten cron or a missed mailbox can still be recovered.&lt;/p&gt;

&lt;p&gt;Or skip all of this: every VPSPioneer plan includes &lt;a href="https://vpspioneer.com/free-migration" rel="noopener noreferrer"&gt;free migration&lt;/a&gt;. Send us the old login and we run exactly this sequence for you.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://vpspioneer.com/blog/migration/move-website-to-new-host-without-downtime" rel="noopener noreferrer"&gt;vpspioneer.com&lt;/a&gt;. More guides on hosting, Linux and servers: &lt;a href="https://vpspioneer.com/blog" rel="noopener noreferrer"&gt;vpspioneer.com/blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>linux</category>
      <category>dns</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Speed Up WordPress on Plesk in 20 Minutes</title>
      <dc:creator>VPSPioneer</dc:creator>
      <pubDate>Thu, 08 Oct 2026 13:28:12 +0000</pubDate>
      <link>https://dev.to/vpspioneer/how-to-speed-up-wordpress-on-plesk-in-20-minutes-1301</link>
      <guid>https://dev.to/vpspioneer/how-to-speed-up-wordpress-on-plesk-in-20-minutes-1301</guid>
      <description>&lt;p&gt;A slow WordPress site is almost never slow because of the server. It is slow because PHP is running on defaults, nothing is cached, and the images are the size they came off the camera. Here is the order we fix these in, from biggest win to smallest.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Switch to PHP-FPM and the newest PHP
&lt;/h2&gt;

&lt;p&gt;In Plesk, go to &lt;strong&gt;Websites &amp;amp; Domains → PHP Settings&lt;/strong&gt; for the domain. Set:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PHP version:&lt;/strong&gt; the newest your plugins support. 8.3 runs typical WordPress ~30% faster than 7.4.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run PHP as:&lt;/strong&gt; &lt;em&gt;FPM application served by nginx&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then check the version from the command line, if you have shell access:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;plesk bin domain &lt;span class="nt"&gt;--info&lt;/span&gt; example.com | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; php
/opt/plesk/php/8.3/bin/php &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Turn on OPcache
&lt;/h2&gt;

&lt;p&gt;OPcache keeps compiled PHP in memory so every request does not re-read every file. Under &lt;strong&gt;PHP Settings → Performance settings&lt;/strong&gt;, or in the &lt;em&gt;Additional directives&lt;/em&gt; box:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="py"&gt;opcache.enable&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;
&lt;span class="py"&gt;opcache.memory_consumption&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;192&lt;/span&gt;
&lt;span class="py"&gt;opcache.max_accelerated_files&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;20000&lt;/span&gt;
&lt;span class="py"&gt;opcache.validate_timestamps&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;
&lt;span class="py"&gt;opcache.revalidate_freq&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;60&lt;/span&gt;
&lt;span class="py"&gt;memory_limit&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;256M&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;revalidate_freq=60&lt;/code&gt; means a file edit shows within a minute; set it to &lt;code&gt;0&lt;/code&gt; on a staging site where you change code constantly.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Let Nginx serve static files and cache pages
&lt;/h2&gt;

&lt;p&gt;Under &lt;strong&gt;Apache &amp;amp; nginx Settings&lt;/strong&gt;, tick &lt;strong&gt;Serve static files directly by nginx&lt;/strong&gt; and enable &lt;strong&gt;nginx caching&lt;/strong&gt; with a 5-minute timeout. Nginx then answers the vast majority of hits without PHP starting at all.&lt;/p&gt;

&lt;p&gt;If the site has a login area, exclude it so users never see each other's pages. In &lt;em&gt;Additional nginx directives&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Never cache logged-in users or the cart&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$http_cookie&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt; &lt;span class="s"&gt;"wordpress_logged_in|woocommerce_items_in_cart")&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;$no_cache&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check a page is coming from cache by looking at the response headers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sI&lt;/span&gt; https://example.com/ | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"x-cache&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;cache-control"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Compress the images that are already uploaded
&lt;/h2&gt;

&lt;p&gt;New uploads can be handled by a plugin, but the existing library is where the weight is. From a shell, convert everything to WebP alongside the originals:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/httpdocs/wp-content/uploads
find &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="se"&gt;\(&lt;/span&gt; &lt;span class="nt"&gt;-iname&lt;/span&gt; &lt;span class="s1"&gt;'*.jpg'&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nt"&gt;-iname&lt;/span&gt; &lt;span class="s1"&gt;'*.png'&lt;/span&gt; &lt;span class="se"&gt;\)&lt;/span&gt; &lt;span class="nt"&gt;-size&lt;/span&gt; +100k &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-exec&lt;/span&gt; sh &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'cwebp -quiet -q 82 "$1" -o "${1%.*}.webp"'&lt;/span&gt; _ &lt;span class="o"&gt;{}&lt;/span&gt; &lt;span class="se"&gt;\;&lt;/span&gt;
&lt;span class="nb"&gt;du&lt;/span&gt; &lt;span class="nt"&gt;-sh&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then let Nginx hand out the &lt;code&gt;.webp&lt;/code&gt; when the browser accepts it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;location&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt; &lt;span class="s"&gt;^/wp-content/uploads/.+&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;.(png|jpe?g)&lt;/span&gt;$ &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Vary&lt;/span&gt; &lt;span class="s"&gt;Accept&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri$webp_suffix&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Clean the database
&lt;/h2&gt;

&lt;p&gt;Post revisions, transients and spam comments accumulate for years. Once, from &lt;strong&gt;Databases → phpMyAdmin&lt;/strong&gt;, or with WP-CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;wp post delete &lt;span class="si"&gt;$(&lt;/span&gt;wp post list &lt;span class="nt"&gt;--post_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'revision'&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ids&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;--force&lt;/span&gt;
wp transient delete &lt;span class="nt"&gt;--all&lt;/span&gt;
wp comment delete &lt;span class="si"&gt;$(&lt;/span&gt;wp comment list &lt;span class="nt"&gt;--status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;spam &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ids&lt;span class="si"&gt;)&lt;/span&gt;
wp db optimize
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then limit future revisions in &lt;code&gt;wp-config.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'WP_POST_REVISIONS'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'AUTOSAVE_INTERVAL'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  6. Put a CDN in front
&lt;/h2&gt;

&lt;p&gt;Cloudflare's free plan is enough for most sites. Point the nameservers at Cloudflare, set SSL mode to &lt;strong&gt;Full (strict)&lt;/strong&gt;, and turn on &lt;strong&gt;Auto Minify&lt;/strong&gt; and &lt;strong&gt;Brotli&lt;/strong&gt;. Static files then come from a node near the visitor rather than from London every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure before and after
&lt;/h2&gt;

&lt;p&gt;Do not guess. Run the site through &lt;a href="https://pagespeed.web.dev/" rel="noopener noreferrer"&gt;PageSpeed Insights&lt;/a&gt; before you start and after each step, and keep the numbers. The metric that matters is &lt;strong&gt;Largest Contentful Paint&lt;/strong&gt;; under 2.5 seconds is the target.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Typical LCP gain&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;PHP-FPM + PHP 8.3&lt;/td&gt;
&lt;td&gt;0.3–0.6 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OPcache&lt;/td&gt;
&lt;td&gt;0.2–0.4 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nginx page cache&lt;/td&gt;
&lt;td&gt;0.5–1.5 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WebP images&lt;/td&gt;
&lt;td&gt;0.5–2 s on image-heavy pages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database cleanup&lt;/td&gt;
&lt;td&gt;small, but stops slow admin pages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CDN&lt;/td&gt;
&lt;td&gt;0.2–0.8 s for visitors far from the server&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every VPSPioneer shared plan already runs PHP-FPM with OPcache and Nginx caching on; steps 1–3 are done for you. On a &lt;a href="https://vpspioneer.com/vps" rel="noopener noreferrer"&gt;managed VPS&lt;/a&gt; we tune all six as part of setup.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://vpspioneer.com/blog/wordpress/speed-up-wordpress-on-plesk" rel="noopener noreferrer"&gt;vpspioneer.com&lt;/a&gt;. More guides on hosting, Linux and servers: &lt;a href="https://vpspioneer.com/blog" rel="noopener noreferrer"&gt;vpspioneer.com/blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>php</category>
      <category>nginx</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Install a Free SSL Certificate in Plesk (Let's Encrypt)</title>
      <dc:creator>VPSPioneer</dc:creator>
      <pubDate>Wed, 07 Oct 2026 13:29:52 +0000</pubDate>
      <link>https://dev.to/vpspioneer/how-to-install-a-free-ssl-certificate-in-plesk-lets-encrypt-28g8</link>
      <guid>https://dev.to/vpspioneer/how-to-install-a-free-ssl-certificate-in-plesk-lets-encrypt-28g8</guid>
      <description>&lt;p&gt;Every site should load over HTTPS. Browsers mark plain HTTP pages as "Not secure", Google uses HTTPS as a ranking signal, and a certificate from Let's Encrypt costs nothing. Plesk has the whole flow built in, so there is no command line needed for the basic case — but we will cover that too, for when you want to script it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you start
&lt;/h2&gt;

&lt;p&gt;You need two things in place:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The domain's &lt;strong&gt;A record&lt;/strong&gt; (and &lt;code&gt;www&lt;/code&gt; if you use it) must already point at your Plesk server. Let's Encrypt validates by fetching a file from your site, so DNS has to resolve first.&lt;/li&gt;
&lt;li&gt;Port &lt;strong&gt;80&lt;/strong&gt; must be reachable. If you have moved everything to 443 with a firewall rule, open 80 for the validation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check DNS from your own machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dig +short example.com A
dig +short www.example.com A
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both should print your server's IP. If they don't, wait for propagation before going further — the request will simply fail otherwise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Issue the certificate from the panel
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;In Plesk, open &lt;strong&gt;Websites &amp;amp; Domains&lt;/strong&gt; and pick the domain.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;SSL/TLS Certificates&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Under &lt;em&gt;Install a free basic certificate provided by Let's Encrypt&lt;/em&gt;, click &lt;strong&gt;Install&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Enter an email address for expiry notices, then tick:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Secure the domain name&lt;/strong&gt; — the site itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Include a "www" subdomain&lt;/strong&gt; — unless you have deliberately never used &lt;code&gt;www&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secure webmail&lt;/strong&gt; and &lt;strong&gt;Assign the certificate to mail domain&lt;/strong&gt; — so your mail client stops warning about &lt;code&gt;mail.example.com&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Get it free&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Plesk requests the certificate, installs it, and switches the site's vhost to it. Reload the domain in a browser: you should see the padlock.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wildcard certificates
&lt;/h2&gt;

&lt;p&gt;If you run many subdomains (&lt;code&gt;shop.example.com&lt;/code&gt;, &lt;code&gt;app.example.com&lt;/code&gt;), issue a wildcard instead so you never do this again per subdomain. Tick &lt;strong&gt;Secure wildcard domain (including www)&lt;/strong&gt;. This uses DNS validation: Plesk shows a &lt;code&gt;_acme-challenge&lt;/code&gt; TXT record to add at your DNS provider. Add it, wait a minute, then click &lt;strong&gt;Continue&lt;/strong&gt;. Verify the record is visible before continuing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dig +short TXT _acme-challenge.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Force HTTPS
&lt;/h2&gt;

&lt;p&gt;A certificate does not redirect anyone by itself. Under &lt;strong&gt;Hosting &amp;amp; DNS → Hosting Settings&lt;/strong&gt;, tick &lt;strong&gt;Permanent SEO-safe 301 redirect from HTTP to HTTPS&lt;/strong&gt;. This is the setting that stops search engines seeing two copies of your site.&lt;/p&gt;

&lt;p&gt;If you prefer to control it in the site itself, an &lt;code&gt;.htaccess&lt;/code&gt; rule does the same:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight apache"&gt;&lt;code&gt;&lt;span class="nc"&gt;RewriteEngine&lt;/span&gt; &lt;span class="ss"&gt;On&lt;/span&gt;
&lt;span class="nc"&gt;RewriteCond&lt;/span&gt; %{HTTPS} !=on
&lt;span class="nc"&gt;RewriteRule&lt;/span&gt; ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  From the command line
&lt;/h2&gt;

&lt;p&gt;The same operation, scriptable — handy when you set up many domains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;plesk bin extension &lt;span class="nt"&gt;--exec&lt;/span&gt; letsencrypt cli.php &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; example.com &lt;span class="nt"&gt;-d&lt;/span&gt; www.example.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-m&lt;/span&gt; you@example.com &lt;span class="nt"&gt;--secure-mail&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To list every certificate on the server with its expiry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;plesk bin certificate &lt;span class="nt"&gt;--list&lt;/span&gt; &lt;span class="nt"&gt;-domain&lt;/span&gt; example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Renewal
&lt;/h2&gt;

&lt;p&gt;Let's Encrypt certificates last 90 days. Plesk's SSL It! extension renews them automatically about 30 days before expiry — there is nothing to schedule. The one thing that breaks renewal is the same thing that breaks issuing: DNS moved away, or port 80 closed. If you get an expiry email, that is where to look.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common errors
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Message&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Invalid response from http://example.com/.well-known/acme-challenge/...&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;DNS points elsewhere, or a redirect intercepts the path&lt;/td&gt;
&lt;td&gt;Check the A record; exclude &lt;code&gt;/.well-known/&lt;/code&gt; from redirects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Too many certificates already issued&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Rate limit: 50 per registered domain per week&lt;/td&gt;
&lt;td&gt;Wait, or use a wildcard instead of many singles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DNS problem: NXDOMAIN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The subdomain has no record&lt;/td&gt;
&lt;td&gt;Add the record and retry&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On VPSPioneer shared and reseller plans this is already done for every new domain — the certificate is issued at account creation and renewed for you. On a &lt;a href="https://vpspioneer.com/vps" rel="noopener noreferrer"&gt;managed VPS&lt;/a&gt; we set it up as part of the handover.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://vpspioneer.com/blog/plesk/install-free-ssl-certificate-plesk" rel="noopener noreferrer"&gt;vpspioneer.com&lt;/a&gt;. More guides on hosting, Linux and servers: &lt;a href="https://vpspioneer.com/blog" rel="noopener noreferrer"&gt;vpspioneer.com/blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>devops</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to Set Up SSH Keys and Turn Off Password Login</title>
      <dc:creator>VPSPioneer</dc:creator>
      <pubDate>Tue, 06 Oct 2026 16:33:46 +0000</pubDate>
      <link>https://dev.to/vpspioneer/how-to-set-up-ssh-keys-and-turn-off-password-login-4m5</link>
      <guid>https://dev.to/vpspioneer/how-to-set-up-ssh-keys-and-turn-off-password-login-4m5</guid>
      <description>&lt;p&gt;A password can be guessed, phished or reused from a leak. An SSH key cannot: it is a 256-bit secret that never leaves your computer, and the server only ever sees a signature. Setting it up takes five minutes and ends password guessing against your server permanently.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Generate a key on your machine
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;macOS and Linux&lt;/strong&gt; — in a terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh-keygen &lt;span class="nt"&gt;-t&lt;/span&gt; ed25519 &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"you@example.com"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Windows 10/11&lt;/strong&gt; — the same command works in PowerShell or Windows Terminal; OpenSSH is built in.&lt;/p&gt;

&lt;p&gt;Accept the default file location (&lt;code&gt;~/.ssh/id_ed25519&lt;/code&gt;). Set a passphrase — it encrypts the private key on disk, so a stolen laptop does not mean a stolen server. You will be asked for it once per session, and an agent remembers it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ed25519&lt;/code&gt; is the modern choice: shorter, faster and at least as secure as a 4096-bit RSA key. Use RSA only if a very old server refuses ed25519.&lt;/p&gt;

&lt;p&gt;You now have two files: &lt;code&gt;id_ed25519&lt;/code&gt; (private — never share, never copy to a server) and &lt;code&gt;id_ed25519.pub&lt;/code&gt; (public — this is what goes on servers).&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Put the public key on the server
&lt;/h2&gt;

&lt;p&gt;The simplest way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh-copy-id deploy@203.0.113.10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It logs in with your password one last time and appends the public key to &lt;code&gt;~/.ssh/authorized_keys&lt;/code&gt;. On Windows without &lt;code&gt;ssh-copy-id&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="kr"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;USERPROFILE&lt;/span&gt;&lt;span class="n"&gt;\.ssh\id_ed25519.pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ssh&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;deploy&lt;/span&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="nx"&gt;203.0.113.10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mkdir -p ~/.ssh &amp;amp;&amp;amp; cat &amp;gt;&amp;gt; ~/.ssh/authorized_keys &amp;amp;&amp;amp; chmod 700 ~/.ssh &amp;amp;&amp;amp; chmod 600 ~/.ssh/authorized_keys"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Plesk, you can also paste the public key under &lt;strong&gt;Websites &amp;amp; Domains → Web Hosting Access → SSH keys&lt;/strong&gt; for the subscription's system user.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Test before changing anything
&lt;/h2&gt;

&lt;p&gt;Open a &lt;strong&gt;new&lt;/strong&gt; terminal and connect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh deploy@203.0.113.10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it logs you in without asking for the account password (only your key passphrase, if you set one), the key works. If it still asks for the password, on the server check the permissions — SSH refuses keys in a directory that is group- or world-writable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;700 ~/.ssh
&lt;span class="nb"&gt;chmod &lt;/span&gt;600 ~/.ssh/authorized_keys
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; ~/.ssh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not go further until this works.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Disable passwords
&lt;/h2&gt;

&lt;p&gt;On the server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nano /etc/ssh/sshd_config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set these three lines (uncomment them if needed):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="err"&gt;PubkeyAuthentication&lt;/span&gt; &lt;span class="err"&gt;yes&lt;/span&gt;
&lt;span class="err"&gt;PasswordAuthentication&lt;/span&gt; &lt;span class="err"&gt;no&lt;/span&gt;
&lt;span class="err"&gt;KbdInteractiveAuthentication&lt;/span&gt; &lt;span class="err"&gt;no&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ubuntu may ship a file in &lt;code&gt;/etc/ssh/sshd_config.d/&lt;/code&gt; that re-enables passwords; check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; PasswordAuthentication /etc/ssh/sshd_config.d/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Validate and restart — keep your current session open while you do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;sshd &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl restart ssh    &lt;span class="c"&gt;# sshd on AlmaLinux / Rocky&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From a third terminal, confirm passwords are refused:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;PubkeyAuthentication&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;no deploy@203.0.113.10
&lt;span class="c"&gt;# Permission denied (publickey).&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Make it convenient
&lt;/h2&gt;

&lt;p&gt;An SSH config file saves typing and lets you use different keys per server. In &lt;code&gt;~/.ssh/config&lt;/code&gt; on your machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="err"&gt;Host&lt;/span&gt; &lt;span class="err"&gt;web&lt;/span&gt;
    &lt;span class="err"&gt;HostName&lt;/span&gt; &lt;span class="err"&gt;203.0.113.10&lt;/span&gt;
    &lt;span class="err"&gt;User&lt;/span&gt; &lt;span class="err"&gt;deploy&lt;/span&gt;
    &lt;span class="err"&gt;IdentityFile&lt;/span&gt; &lt;span class="err"&gt;~/.ssh/id_ed25519&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now &lt;code&gt;ssh web&lt;/code&gt; connects. On macOS, add &lt;code&gt;UseKeychain yes&lt;/code&gt; under the host and the passphrase is stored in Keychain. On Windows, start the agent once: &lt;code&gt;Get-Service ssh-agent | Set-Service -StartupType Automatic; Start-Service ssh-agent; ssh-add&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Several people, several keys
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;authorized_keys&lt;/code&gt; is one public key per line. Add each person's key on its own line, and remove the line when they leave — that is offboarding done. Keep a comment on each line (the &lt;code&gt;-C&lt;/code&gt; you set when generating) so you know whose key is whose.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you lose the private key
&lt;/h2&gt;

&lt;p&gt;You cannot recover it, by design. Log in through your provider's console (VPSPioneer: the client area, or a ticket on a &lt;a href="https://vpspioneer.com/vps" rel="noopener noreferrer"&gt;managed VPS&lt;/a&gt;), add a new public key to &lt;code&gt;authorized_keys&lt;/code&gt;, and remove the old line. This is also why the console exists: it works without SSH at all.&lt;/p&gt;

&lt;p&gt;With keys in place and passwords off, the next two steps in &lt;a href="https://vpspioneer.com/blog/security/harden-a-new-linux-vps-in-10-steps" rel="noopener noreferrer"&gt;hardening a VPS&lt;/a&gt; — a firewall and Fail2ban — turn the ten thousand failed logins a day in your logs into zero.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://vpspioneer.com/blog/linux/ssh-keys-disable-password-login" rel="noopener noreferrer"&gt;vpspioneer.com&lt;/a&gt;. More guides on hosting, Linux and servers: &lt;a href="https://vpspioneer.com/blog" rel="noopener noreferrer"&gt;vpspioneer.com/blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>linux</category>
      <category>ssh</category>
      <category>security</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
