{"id":1628,"date":"2025-11-10T18:17:19","date_gmt":"2025-11-10T15:17:19","guid":{"rendered":"https:\/\/www.dchost.com\/blog\/wordpress-hardening-checklist-the-friendly-way-to-lock-down-file-permissions-salt-keys-xml-rpc-and-ufw-fail2ban\/"},"modified":"2025-11-10T18:17:19","modified_gmt":"2025-11-10T15:17:19","slug":"wordpress-hardening-checklist-the-friendly-way-to-lock-down-file-permissions-salt-keys-xml-rpc-and-ufw-fail2ban","status":"publish","type":"post","link":"https:\/\/www.dchost.com\/blog\/en\/wordpress-hardening-checklist-the-friendly-way-to-lock-down-file-permissions-salt-keys-xml-rpc-and-ufw-fail2ban\/","title":{"rendered":"WordPress Hardening Checklist: The Friendly Way to Lock Down File Permissions, Salt Keys, XML-RPC, and UFW\/Fail2ban"},"content":{"rendered":"<div class=\"dchost-blog-content-wrapper\"><div id=\"toc_container\" class=\"toc_transparent no_bullets\"><p class=\"toc_title\">\u0130&ccedil;indekiler<\/p><ul class=\"toc_list\"><li><a href=\"#The_Morning_I_Realized_My_WordPress_Needed_a_Bodyguard\"><span class=\"toc_number toc_depth_1\">1<\/span> The Morning I Realized My WordPress Needed a Bodyguard<\/a><\/li><li><a href=\"#Before_We_Tighten_Bolts_A_Calm_Baseline\"><span class=\"toc_number toc_depth_1\">2<\/span> Before We Tighten Bolts: A Calm Baseline<\/a><\/li><li><a href=\"#File_Permissions_That_Dont_Fight_You_or_Invite_Strangers\"><span class=\"toc_number toc_depth_1\">3<\/span> File Permissions That Don\u2019t Fight You (or Invite Strangers)<\/a><ul><li><a href=\"#Who_owns_whatand_why_it_matters\"><span class=\"toc_number toc_depth_2\">3.1<\/span> Who owns what\u2014and why it matters<\/a><\/li><li><a href=\"#The_tidy_defaults_that_just_work\"><span class=\"toc_number toc_depth_2\">3.2<\/span> The tidy defaults that just work<\/a><\/li><li><a href=\"#A_quiet_extra_disable_file_editing_via_the_dashboard\"><span class=\"toc_number toc_depth_2\">3.3<\/span> A quiet extra: disable file editing via the dashboard<\/a><\/li><\/ul><\/li><li><a href=\"#Salt_Keys_The_Little_Locks_That_Keep_Sessions_Honest\"><span class=\"toc_number toc_depth_1\">4<\/span> Salt Keys: The Little Locks That Keep Sessions Honest<\/a><ul><li><a href=\"#How_to_rotate_salt_keys_without_upsetting_anyone\"><span class=\"toc_number toc_depth_2\">4.1<\/span> How to rotate salt keys without upsetting anyone<\/a><\/li><\/ul><\/li><li><a href=\"#XML-RPC_When_to_Keep_It_When_to_Put_It_on_a_Short_Leash\"><span class=\"toc_number toc_depth_1\">5<\/span> XML-RPC: When to Keep It, When to Put It on a Short Leash<\/a><ul><li><a href=\"#The_easy_off_switch\"><span class=\"toc_number toc_depth_2\">5.1<\/span> The easy off switch<\/a><\/li><\/ul><\/li><li><a href=\"#UFW_Fail2ban_The_Doormen_Who_Dont_Mess_Around\"><span class=\"toc_number toc_depth_1\">6<\/span> UFW + Fail2ban: The Doormen Who Don\u2019t Mess Around<\/a><ul><li><a href=\"#UFW_Default_deny_then_invite_who_matters\"><span class=\"toc_number toc_depth_2\">6.1<\/span> UFW: Default deny, then invite who matters<\/a><\/li><li><a href=\"#Fail2ban_Real-time_timeout_for_noisy_neighbors\"><span class=\"toc_number toc_depth_2\">6.2<\/span> Fail2ban: Real-time timeout for noisy neighbors<\/a><\/li><\/ul><\/li><li><a href=\"#Putting_It_All_Together_A_Calm_CopyFriendly_Checklist\"><span class=\"toc_number toc_depth_1\">7<\/span> Putting It All Together: A Calm, Copy\u2011Friendly Checklist<\/a><ul><li><a href=\"#1_Confirm_your_footing\"><span class=\"toc_number toc_depth_2\">7.1<\/span> 1) Confirm your footing<\/a><\/li><li><a href=\"#2_Fix_ownership_and_permissions\"><span class=\"toc_number toc_depth_2\">7.2<\/span> 2) Fix ownership and permissions<\/a><\/li><li><a href=\"#3_Disable_file_editing_in_the_dashboard\"><span class=\"toc_number toc_depth_2\">7.3<\/span> 3) Disable file editing in the dashboard<\/a><\/li><li><a href=\"#4_Rotate_the_salt_keys\"><span class=\"toc_number toc_depth_2\">7.4<\/span> 4) Rotate the salt keys<\/a><\/li><li><a href=\"#5_Decide_the_fate_of_XML-RPC\"><span class=\"toc_number toc_depth_2\">7.5<\/span> 5) Decide the fate of XML-RPC<\/a><\/li><li><a href=\"#6_Enable_UFW_then_teach_Fail2ban_to_be_your_bouncer\"><span class=\"toc_number toc_depth_2\">7.6<\/span> 6) Enable UFW, then teach Fail2ban to be your bouncer<\/a><\/li><li><a href=\"#7_Keep_an_ear_on_the_room\"><span class=\"toc_number toc_depth_2\">7.7<\/span> 7) Keep an ear on the room<\/a><\/li><\/ul><\/li><li><a href=\"#A_Few_RealWorld_Moments_That_Changed_How_I_Harden_Sites\"><span class=\"toc_number toc_depth_1\">8<\/span> A Few Real\u2011World Moments That Changed How I Harden Sites<\/a><\/li><li><a href=\"#WrapUp_The_Friendly_Way_to_Stay_Secure_Without_Losing_Sleep\"><span class=\"toc_number toc_depth_1\">9<\/span> Wrap\u2011Up: The Friendly Way to Stay Secure Without Losing Sleep<\/a><\/li><\/ul><\/div>\n<h2 id=\"section-1\"><span id=\"The_Morning_I_Realized_My_WordPress_Needed_a_Bodyguard\">The Morning I Realized My WordPress Needed a Bodyguard<\/span><\/h2>\n<p>I still remember the morning a client pinged me with the kind of message that makes your coffee suddenly taste like battery acid: \u201cOur site is slow, weird logins, and I can\u2019t save posts.\u201d We\u2019d all been there\u2014plugin updates that went sideways, login spam that looked like a tidal wave, and some mystery process chewing CPU like it was popcorn. The culprit, as it turned out, wasn\u2019t just one thing. It was a combination of soft underbelly settings: loose file permissions, stale salt keys, a wide-open XML-RPC endpoint, and a server without a proper firewall bouncer at the door.<\/p>\n<p>Hardening WordPress isn\u2019t about making it awkward to work with. It\u2019s more like setting house rules: where the keys live, which doors stay locked, who\u2019s allowed to knock, and what happens if someone keeps hammering the bell. In this guide, I\u2019ll walk you through a warm, practical checklist that starts with file permissions that actually make sense, rotates those secret salt keys that quietly protect your sessions, puts XML-RPC into context (and a leash), and finishes with a one-two combo of UFW and Fail2ban to keep the riffraff outside.<\/p>\n<p>If you\u2019ve ever had that moment where you thought \u201cI\u2019ll lock this down tomorrow,\u201d think of this as your friendly nudge. We\u2019ll go step by step, with concrete examples you can copy, and a few stories from the trenches so it feels less like homework and more like tidying up your digital living room before guests arrive.<\/p>\n<h2 id=\"section-2\"><span id=\"Before_We_Tighten_Bolts_A_Calm_Baseline\">Before We Tighten Bolts: A Calm Baseline<\/span><\/h2>\n<p>Here\u2019s the thing about security: it\u2019s easier when your basics are already peaceful. I like to start by making sure backups exist and are verified, staging works, and we know who owns the files on disk. When that foundation is in place, every hardening step feels like a calm upgrade rather than a gamble.<\/p>\n<p>Two quick things make a big difference. First, if you manage your WordPress on a <a href=\"https:\/\/www.dchost.com\/vps\">VPS<\/a>, keep your SSH access in good shape\u2014keys instead of passwords, maybe a hardware key if you can swing it, and a plan for rotating them. I shared a step-by-step I use myself in <a href=\"https:\/\/www.dchost.com\/blog\/en\/vpste-ssh-guvenligi-nasil-saglamlasir-fido2-anahtarlari-ssh-ca-ve-rotasyonun-sicacik-yolculugu\/\">VPS SSH Hardening Without the Drama<\/a>. Second, know how you\u2019ll watch your logs so you can catch patterns early. If you\u2019re curious, my playbook for clean, central logs is in <a href=\"https:\/\/www.dchost.com\/blog\/en\/vps-log-yonetimi-nasil-rayina-oturur-grafana-loki-promtail-ile-merkezi-loglama-tutma-sureleri-ve-alarm-kurallari\/\">VPS Log Management Without the Drama<\/a>. You don\u2019t need the whole observability orchestra today\u2014just enough to hear when someone\u2019s playing the wrong notes.<\/p>\n<p>Okay, baseline done. Let\u2019s tighten some bolts.<\/p>\n<h2 id=\"section-3\"><span id=\"File_Permissions_That_Dont_Fight_You_or_Invite_Strangers\">File Permissions That Don\u2019t Fight You (or Invite Strangers)<\/span><\/h2>\n<p>When I first started hosting WordPress for clients, I saw all sorts of \u201ccreative\u201d file permissions. Someone, somewhere, had told them that 777 is a magic fix. It is magic, just not the good kind. The safer truth is simple: the web server should be able to read what it serves, write only where it must, and nothing more.<\/p>\n<h3><span id=\"Who_owns_whatand_why_it_matters\">Who owns what\u2014and why it matters<\/span><\/h3>\n<p>On a typical Linux server with Nginx or Apache, your web server user might be <strong>www-data<\/strong>, <strong>apache<\/strong>, or <strong>nginx<\/strong>. Ideally, the files live under a user account you control (say <em>deploy<\/em> or <em>wpuser<\/em>), with the web server group granted the right access. This gives you a clean boundary: you can deploy and edit files as yourself, while the server only gets exactly what it needs at runtime.<\/p>\n<p>In practice, that means setting ownership like this (adjust paths and users to your world):<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">cd \/var\/www\/example.com\nsudo chown -R wpuser:www-data .\n<\/code><\/pre>\n<p>Now, about permissions.<\/p>\n<h3><span id=\"The_tidy_defaults_that_just_work\">The tidy defaults that just work<\/span><\/h3>\n<p>Most WordPress files can safely be <strong>644<\/strong> (readable by all, writable by owner) and directories <strong>755<\/strong> (enterable by all, writable by owner). That balance lets the web server read your site while keeping write access limited. So, a simple sweep like this goes a long way:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">find \/var\/www\/example.com -type d -exec chmod 755 {} ;\nfind \/var\/www\/example.com -type f -exec chmod 644 {} ;\n<\/code><\/pre>\n<p>There are two special cases. First, <strong>wp-config.php<\/strong> holds your secrets, so give it extra love:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">chmod 640 \/var\/www\/example.com\/wp-config.php\n<\/code><\/pre>\n<p>Second, wherever PHP needs to write\u2014usually <strong>wp-content\/uploads<\/strong>, sometimes <strong>wp-content\/cache<\/strong>\u2014you can grant group write permission by setting directories to 775 and files to 664. That way, the server can save uploads without giving away the keys to the entire kingdom.<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">find \/var\/www\/example.com\/wp-content\/uploads -type d -exec chmod 775 {} ;\nfind \/var\/www\/example.com\/wp-content\/uploads -type f -exec chmod 664 {} ;\n<\/code><\/pre>\n<p>In my experience, most \u201cmy site got hacked\u201d stories involve a weak link: a vulnerable plugin and a place the server could write freely. The permissions above narrow the blast radius. It\u2019s not a fortress on its own, but it turns canvas doors into solid wood.<\/p>\n<h3><span id=\"A_quiet_extra_disable_file_editing_via_the_dashboard\">A quiet extra: disable file editing via the dashboard<\/span><\/h3>\n<p>Even if you trust everyone with wp-admin access, it\u2019s smart to turn off the built-in theme and plugin editors. It removes a whole pathway for accidental changes\u2014or intentional misuse. Pop this into <strong>wp-config.php<\/strong>:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">define('DISALLOW_FILE_EDIT', true);\n<\/code><\/pre>\n<p>Want the canonical guidance on file permissions and general hardening? The official page is a helpful reference when you want to sanity-check yourself: <a href=\"https:\/\/wordpress.org\/documentation\/article\/hardening-wordpress\/\" rel=\"nofollow noopener\" target=\"_blank\">WordPress hardening guide<\/a>.<\/p>\n<h2 id=\"section-4\"><span id=\"Salt_Keys_The_Little_Locks_That_Keep_Sessions_Honest\">Salt Keys: The Little Locks That Keep Sessions Honest<\/span><\/h2>\n<p>Here\u2019s a secret almost nobody talks about at first: your WordPress <strong>salt keys<\/strong> are like the pins in a lock. They help protect sessions and cookies from being guessed or forged. If those keys are old, copied from a public example, or floating around in a repo, you\u2019re basically leaving your spare key under the mat.<\/p>\n<h3><span id=\"How_to_rotate_salt_keys_without_upsetting_anyone\">How to rotate salt keys without upsetting anyone<\/span><\/h3>\n<p>Rotating salts is one of those \u201cfive-minute chores that pays off for months.\u201d When you update them, any logged-in sessions get kicked out and must sign in again\u2014slightly annoying, but worth it. To do it quickly, use the official generator:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\"># Get fresh salts from WordPress API and replace them in wp-config.php\ncurl -s https:\/\/api.wordpress.org\/secret-key\/1.1\/salt\/\n<\/code><\/pre>\n<p>Copy the output and replace the corresponding lines in <strong>wp-config.php<\/strong>. Do this any time you suspect creds were exposed, a developer left the team, or after a major incident. If you\u2019re comfortable with WP-CLI, there\u2019s an even breezier option:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">wp config shuffle-salts\n<\/code><\/pre>\n<p>I\u2019ve had clients ask, \u201cBut won\u2019t this break the site?\u201d The site keeps serving pages just fine. Only existing login sessions are invalidated\u2014and sometimes that\u2019s exactly what you want after tightening things up.<\/p>\n<p>Bookmark the generator for when you need it in a hurry: <a href=\"https:\/\/api.wordpress.org\/secret-key\/1.1\/salt\/\" rel=\"nofollow noopener\" target=\"_blank\">WordPress secret-key service<\/a>.<\/p>\n<h2 id=\"section-5\"><span id=\"XML-RPC_When_to_Keep_It_When_to_Put_It_on_a_Short_Leash\">XML-RPC: When to Keep It, When to Put It on a Short Leash<\/span><\/h2>\n<p>XML-RPC is like that side door you rarely use. It\u2019s there for remote publishing, some mobile apps, and certain services that connect to your WordPress from afar. Problem is, bots love hammering it\u2014especially to brute-force logins or trigger resource-heavy actions.<\/p>\n<p>I\u2019ve seen two kinds of teams: those who don\u2019t need XML-RPC at all, and those who depend on it for a handful of workflows (Jetpack, mobile posting, external editors). For the first group, the simplest path is to <strong>disable it completely<\/strong>. For the second, the goal is to <strong>allow the few things you need and block the rest<\/strong>.<\/p>\n<h3><span id=\"The_easy_off_switch\">The easy off switch<\/span><\/h3>\n<p>If you\u2019re on Apache and not using XML-RPC, you can short-circuit requests in <strong>.htaccess<\/strong>:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">&lt;Files xmlrpc.php&gt;\n  Order allow,deny\n  Deny from all\n&lt;\/Files&gt;\n<\/code><\/pre>\n<p>On Nginx, a location block does the same job:<\/p>\n<pre class=\"language-nginx line-numbers\"><code class=\"language-nginx\">location = \/xmlrpc.php {\n    deny all;\n}\n<\/code><\/pre>\n<p>When you absolutely must keep it, rate limiting and IP allowlists can calm things down. And this is where a friendly combo of Nginx rules plus Fail2ban shines. If you want a deeper dive into the mechanics, I wrote about it in <a href=\"https:\/\/www.dchost.com\/blog\/en\/nginx-rate-limiting-ve-fail2ban-ile-wp%E2%80%91login-php-ve-xml%E2%80%91rpc-brute%E2%80%91force-saldirilarini-nasil-saksiya-alirsin\/\">the calm way to stop wp-login.php and XML-RPC brute force<\/a>. It\u2019s the same idea we\u2019ll use in the next section, but with gentler hands for endpoints you still care about.<\/p>\n<p>One more practical tip: if you\u2019re using an external service and aren\u2019t sure whether it needs XML-RPC, try temporarily disabling it and watch the service logs. If something breaks, you\u2019ve learned where the dependency lives; if not, you\u2019ve removed a noisy door from your house.<\/p>\n<h2 id=\"section-6\"><span id=\"UFW_Fail2ban_The_Doormen_Who_Dont_Mess_Around\">UFW + Fail2ban: The Doormen Who Don\u2019t Mess Around<\/span><\/h2>\n<p>When it comes to server-level defense, I picture two very polite bouncers at the club door. <strong>UFW<\/strong> decides who can even come near the building. <strong>Fail2ban<\/strong> watches the line, and if someone keeps jabbing at the doorbell, they take a quick walk down the street for a cooldown. Together, they reduce the noise so your app and database don\u2019t have to deal with tantrums.<\/p>\n<h3><span id=\"UFW_Default_deny_then_invite_who_matters\">UFW: Default deny, then invite who matters<\/span><\/h3>\n<p>UFW is like flipping on a simple \u201conly these ports are allowed\u201d switch. On Ubuntu, here\u2019s my usual starting point:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\"># Allow SSH, then Web (HTTP\/HTTPS)\nsudo ufw allow OpenSSH\nsudo ufw allow 80\nsudo ufw allow 443\n\n# Default rules: block incoming, allow outgoing\nsudo ufw default deny incoming\nsudo ufw default allow outgoing\n\n# Enable and check\nsudo ufw enable\nsudo ufw status verbose\n<\/code><\/pre>\n<p>That\u2019s enough to keep life quiet on most VPS setups. If you\u2019re curious about deeper patterns\u2014rate limiting, port knocking, IPv6 nuance\u2014I\u2019ve shared a complete firewall cookbook with nftables as an alternative to UFW in <a href=\"https:\/\/www.dchost.com\/blog\/en\/nftables-ile-vps-guvenlik-duvari-rehberi-rate-limit-port-knocking-ve-ipv6-kurallari-nasil-tatli-tatli-kurulur\/\">this firewall playbook<\/a>. Prefer the official UFW docs? Keep this in your pocket: <a href=\"https:\/\/help.ubuntu.com\/community\/UFW\" rel=\"nofollow noopener\" target=\"_blank\">Ubuntu UFW documentation<\/a>.<\/p>\n<h3><span id=\"Fail2ban_Real-time_timeout_for_noisy_neighbors\">Fail2ban: Real-time timeout for noisy neighbors<\/span><\/h3>\n<p>Fail2ban lives off your logs and makes snap decisions: \u201cThis IP tried ten passwords in 30 seconds? They can sit out for 15 minutes.\u201d It\u2019s not perfect, but I love how it takes constant nagging off the table. Here\u2019s a lightweight way to tackle both <strong>wp-login.php<\/strong> and <strong>xmlrpc.php<\/strong> with Nginx access logs.<\/p>\n<p>First, create a filter for WordPress login abuse. Save this as <strong>\/etc\/fail2ban\/filter.d\/wordpress-login.conf<\/strong>:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">[Definition]\nfailregex = ^&lt;HOST&gt; -.*POST \/wp-login.php HTTP\/.*\nignoreregex =\n<\/code><\/pre>\n<p>Now a filter for XML-RPC brute force (lots of POSTs to xmlrpc.php):<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">[Definition]\nfailregex = ^&lt;HOST&gt; -.*POST \/xmlrpc.php HTTP\/.*\nignoreregex =\n<\/code><\/pre>\n<p>Next, wire them into a jail. In <strong>\/etc\/fail2ban\/jail.local<\/strong>, add:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">[wordpress-login]\nenabled  = true\nport     = http,https\nfilter   = wordpress-login\nlogpath  = \/var\/log\/nginx\/access.log\nmaxretry = 5\nfindtime = 600\nbantime  = 900\n\n[wordpress-xmlrpc]\nenabled  = true\nport     = http,https\nfilter   = wordpress-xmlrpc\nlogpath  = \/var\/log\/nginx\/access.log\nmaxretry = 10\nfindtime = 600\nbantime  = 1800\n<\/code><\/pre>\n<p>Reload Fail2ban to apply changes:<\/p>\n<pre class=\"language-bash line-numbers\"><code class=\"language-bash\">sudo systemctl restart fail2ban\nsudo fail2ban-client status wordpress-login\nsudo fail2ban-client status wordpress-xmlrpc\n<\/code><\/pre>\n<p>You\u2019ll start seeing bans for repeat offenders pretty quickly. If your access logs are elsewhere or you\u2019re on Apache, just point <strong>logpath<\/strong> to the right file. And if you want to be extra nice to real users, add Nginx rate limiting to \u201cslow down\u201d bots before they trip the ban. I go into rate limits and practical thresholds in <a href=\"https:\/\/www.dchost.com\/blog\/en\/nginx-rate-limiting-ve-fail2ban-ile-wp%E2%80%91login-php-ve-xml%E2%80%91rpc-brute%E2%80%91force-saldirilarini-nasil-saksiya-alirsin\/\">this gentle anti-brute-force guide<\/a> too.<\/p>\n<p>One last note from the field: tune bans to your audience. For internal dashboards with a small team, a shorter <strong>findtime<\/strong> and longer <strong>bantime<\/strong> make sense. For public sites with varied traffic, be generous with retries but strict with cold repeaters. You\u2019re balancing kindness with boundaries.<\/p>\n<h2 id=\"section-7\"><span id=\"Putting_It_All_Together_A_Calm_CopyFriendly_Checklist\">Putting It All Together: A Calm, Copy\u2011Friendly Checklist<\/span><\/h2>\n<p>This is the flow I use when I\u2019m hardening a WordPress site without drama. It reads like a story because I\u2019ve learned to follow it in this order to avoid surprises.<\/p>\n<h3><span id=\"1_Confirm_your_footing\">1) Confirm your footing<\/span><\/h3>\n<p>Take a fresh backup and verify you can restore it. Make sure you have shell access with keys and a way to view logs comfortably. If you\u2019re building a new VPS, get your SSH basics right first\u2014my walkthrough in <a href=\"https:\/\/www.dchost.com\/blog\/en\/vpste-ssh-guvenligi-nasil-saglamlasir-fido2-anahtarlari-ssh-ca-ve-rotasyonun-sicacik-yolculugu\/\">this SSH hardening guide<\/a> is a good warm-up.<\/p>\n<h3><span id=\"2_Fix_ownership_and_permissions\">2) Fix ownership and permissions<\/span><\/h3>\n<p>Set ownership so your deploy user owns the files and the web server is the group. Sweep directories to 755, files to 644. Give <strong>wp-config.php<\/strong> the 640 treatment. Allow group write only where the server must write\u2014uploads, cache, maybe a custom storage directory you know by heart.<\/p>\n<h3><span id=\"3_Disable_file_editing_in_the_dashboard\">3) Disable file editing in the dashboard<\/span><\/h3>\n<p>Add <code>define('DISALLOW_FILE_EDIT', true); <\/code> to <strong>wp-config.php<\/strong>. It\u2019s one small line that closes a door you\u2019ll rarely miss.<\/p>\n<h3><span id=\"4_Rotate_the_salt_keys\">4) Rotate the salt keys<\/span><\/h3>\n<p>Use the <a href=\"https:\/\/api.wordpress.org\/secret-key\/1.1\/salt\/\" rel=\"nofollow noopener\" target=\"_blank\">official salt generator<\/a> or <code>wp config shuffle-salts<\/code>. Tell your team logins will reset\u2014make it a quick five-minute window, ideally during a low-traffic moment.<\/p>\n<h3><span id=\"5_Decide_the_fate_of_XML-RPC\">5) Decide the fate of XML-RPC<\/span><\/h3>\n<p>If you don\u2019t use it, deny access outright in your web server. If you do use it, restrict by IP if possible, rate limit, and prepare a Fail2ban rule for repeat POST attempts. If you want a battle-tested template, borrow from <a href=\"https:\/\/www.dchost.com\/blog\/en\/nginx-rate-limiting-ve-fail2ban-ile-wp%E2%80%91login-php-ve-xml%E2%80%91rpc-brute%E2%80%91force-saldirilarini-nasil-saksiya-alirsin\/\">my calm rate-limiting + Fail2ban setup<\/a>.<\/p>\n<h3><span id=\"6_Enable_UFW_then_teach_Fail2ban_to_be_your_bouncer\">6) Enable UFW, then teach Fail2ban to be your bouncer<\/span><\/h3>\n<p>Open only what you need\u2014SSH, HTTP, HTTPS\u2014and default deny the rest. Then let Fail2ban watch the access logs for wp-login.php and xmlrpc.php and hand out short bans to repeat offenders. If you feel like leveling up, I share a more advanced firewall strategy with nftables in <a href=\"https:\/\/www.dchost.com\/blog\/en\/nftables-ile-vps-guvenlik-duvari-rehberi-rate-limit-port-knocking-ve-ipv6-kurallari-nasil-tatli-tatli-kurulur\/\">this firewall cookbook<\/a>.<\/p>\n<h3><span id=\"7_Keep_an_ear_on_the_room\">7) Keep an ear on the room<\/span><\/h3>\n<p>Watch your logs for a day or two. You\u2019ll spot patterns quickly\u2014bots that love a certain path, a plugin throwing loud warnings, or a wave of 403s because you got a bit too strict. Dial things up or down as you learn. If you want to go further, I\u2019ve laid out a friendly way to centralize and search logs in <a href=\"https:\/\/www.dchost.com\/blog\/en\/vps-log-yonetimi-nasil-rayina-oturur-grafana-loki-promtail-ile-merkezi-loglama-tutma-sureleri-ve-alarm-kurallari\/\">this guide to Loki + Promtail<\/a>.<\/p>\n<h2 id=\"section-8\"><span id=\"A_Few_RealWorld_Moments_That_Changed_How_I_Harden_Sites\">A Few Real\u2011World Moments That Changed How I Harden Sites<\/span><\/h2>\n<p>One of my clients ran a busy blog with guest writers scattered across time zones. They depended on a mobile app that leaned on XML-RPC. Disabling it wasn\u2019t an option, but the brute-force noise was driving their CPU through the roof. The fix that finally stuck was a mix of three small tweaks: a strict Nginx rate limit on <strong>\/xmlrpc.php<\/strong>, a Fail2ban rule that only bans on multiple failed logins (not just any POST), and a narrow allowlist for the app\u2019s known IP ranges. Traffic stayed smooth, and the attack graphs looked like someone shut off a faucet.<\/p>\n<p>Another team inherited a site where every file was writable by everyone. It had \u201cworked\u201d for years\u2014until it didn\u2019t. The switch to sensible ownership and permissions took an hour, but the relief was instant. Suddenly, backups didn\u2019t include malware stubs in every directory, and their SFTP wasn\u2019t a minefield. We also tossed in <strong>DISALLOW_FILE_EDIT<\/strong> and rotated salt keys for good measure. A month later, they messaged me saying, \u201cWe\u2019ve had fewer scary alerts this month than in any month last year.\u201d It wasn\u2019t magic, just housekeeping.<\/p>\n<p>I share these not to brag, but to remind you: small, boring steps add up. You don\u2019t need a PhD in cryptography to keep a WordPress site tidy and resilient. You just need a rhythm and a checklist.<\/p>\n<h2 id=\"section-9\"><span id=\"WrapUp_The_Friendly_Way_to_Stay_Secure_Without_Losing_Sleep\">Wrap\u2011Up: The Friendly Way to Stay Secure Without Losing Sleep<\/span><\/h2>\n<p>If you take only one thing from this, let it be this: hardening WordPress is less about paranoia and more about <strong>boundaries<\/strong>. Reasonable file permissions so your server isn\u2019t a free-for-all. Fresh salt keys so sessions are trustworthy and disposable. XML-RPC on a need-to-use basis, not by default. And a calm pair of bouncers\u2014UFW to run the door, Fail2ban to handle the occasional loudmouth.<\/p>\n<p>Make these steps part of your routine. Put a quarterly reminder to rotate salts. Review permissions after big deployments or team changes. Revisit your XML-RPC choice when workflows evolve. And keep your firewall rules simple enough that Future You remembers why they exist. If you want to go deeper into related topics, I\u2019ve written about <a href=\"https:\/\/www.dchost.com\/blog\/en\/nginx-mikro-onbellekleme-ile-php-uygulamalarini-ucurmak-1-5-sn-cache-bypass-ve-purge-ne-zaman-nasil\/\">Nginx microcaching to make PHP feel snappy<\/a> and how smarter server patterns keep your site resilient even on busy days.<\/p>\n<p>Hope this was helpful! If you\u2019ve got stories from the trenches or a question I didn\u2019t cover, drop me a note. I love hearing how others keep their WordPress sites safe without the drama.<\/p>\n<\/div>","protected":false},"excerpt":{"rendered":"<p>\u0130&ccedil;indekiler1 The Morning I Realized My WordPress Needed a Bodyguard2 Before We Tighten Bolts: A Calm Baseline3 File Permissions That Don\u2019t Fight You (or Invite Strangers)3.1 Who owns what\u2014and why it matters3.2 The tidy defaults that just work3.3 A quiet extra: disable file editing via the dashboard4 Salt Keys: The Little Locks That Keep Sessions [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1629,"comment_status":"","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[26],"tags":[],"class_list":["post-1628","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-teknoloji"],"_links":{"self":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/posts\/1628","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/comments?post=1628"}],"version-history":[{"count":0,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/posts\/1628\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/media\/1629"}],"wp:attachment":[{"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/media?parent=1628"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/categories?post=1628"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.dchost.com\/blog\/en\/wp-json\/wp\/v2\/tags?post=1628"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}