Wordpress: How to properly hide Wordpress?

Wordpress: How to properly hide Wordpress?

Jedan od najbitnijih načina da sakriješ WordPress je da modifikuješ strukturu datoteka ili da je nekako prikažeš drugačiju.

security - Steps to Take to Hide the Fact a Site is Using WordPress? - WordPress Development Stack Exchange

How to Hide that You Use Wordpress How To Hide The Fact That Your Website Runs On WordPress | Elegant Themes Blog Best Plugins to Hide WordPress How to Hide That Your Site is Using WordPress | kevinleary.net

Plugin: Hide My WP

It supports Nginx perfectly - as per this answer from developer.

I don’t like it as it captures all the output and replaces strings in realtime. I think this is simply to raw and non-performant.

Put everything that plugin suggests:

nano /var/www/vhosts/save-up.ch/conf.d/wp-hide-my-wp.conf

And don’t forget to include that file:


echo '

# Hide My WP Plugin Config
include /var/www/vhosts/save-up.ch/conf.d/wp-hide-my-wp.conf;

' >> /var/www/vhosts/system/save-up.ch/conf/vhost_nginx.conf

Disabled for now.

Remove the WordPress Generator meta tags

Remove Generator Meta Tag that is inserted by the Theme in Wordpress:

WordPress › WP Remove Generator Meta Tag « WordPress Plugins WordPress › Support » [Plugin: WP Remove Generator Meta Tag] Generator info is still generated on all pages…

The right way: The Right Way to Remove WordPress Version Number

?? How to remove the WordPress Generator Meta Tag

Hide WP

Detect what it is?

Idi jedan po jedan, i pokreći wpscan, i kada ga ne detektuje - znaćeš šta je.

Hide My WP - Amazing Security Plugin for WordPress! by wpWave | CodeCanyon Hide My WP Demo – Hide the fact you use WordPress!


Wpscan doesn’t recognize WP if:

  1. Needed hiding specified in my wordpress.conf
  2. Hide Login Page
  3. New wp-content path (or sometimes enough is New theme path + New upload path + New plugin path). All this requires some lines in wordpress.conf.

Appspector - extension (Chrome takođe) (ex: Chrome Sniffer) http://www.nqbao.com/blog/appspector/

  • Za njega je dovoljno da se uključi: Other Meta / Remove other header metas like short link, previous and next links, etc.

Wappylizer (https://github.com/AliasIO/Wappalyzer/) still recognizes it:

  • Wappylizer (koristi isključivo u Chrome jer se tamo trenutno updateuje - ma i tamo je iz cache-a)

Ma jok, nego se mora clearovati lokalni cache za extenziju. Disableuješ pa enableuješ extensziju - to sigurno radi.

Wappylizer is the same, but better is to set new wp-content path, as only in that way it will not catch that it is wordpress.

For those detection engines, it is ok to leave wp-content folder still active, just in html there should be no mention of it.

Pokusao sam na 100 načina, ali sam disable-enable radi.

Here are all the Wappylizer rules:


Also, I tried to use PHP driver:

apt-get -y install libv8-dev php-pear php-dev
pecl install channel://pecl.php.net/v8js-0.1.3

I also tried using PhantomJS:

apt-get -y install phantomjs

# Due to some bug, we also need this
apt-get -y install xvfb

# And we must wrap every call
xvfb-run phantomjs hello.js

# As explained here: https://github.com/Arachni/arachni/issues/707#issuecomment-215468415
alias phantomjs="xvfb-run phantomjs"
phantomjs driver.js https://www.koviljaca.rs -v

But dropped that as I realized that bookmarklet is not cached. It only needed to change one http:// to https:// as bookmarklet had that small error.

So, the perfect bookmarklet that is not cached:

javascript: (function() { var d = document, e = d.getElementById('wappalyzer-container') ; if ( e !== null ) { d.body.removeChild(e); } var u = 'https://wappalyzer.com/bookmarklet/', t = new Date().getTime(), c = d.createElement('div'), p = d.createElement('div'), l = d.createElement('link'), s = d.createElement('script') ; c.setAttribute('id', 'wappalyzer-container'); l.setAttribute('rel', 'stylesheet'); l.setAttribute('href', u + 'css/wappalyzer.css'); d.head.appendChild(l); p.setAttribute('id', 'wappalyzer-pending'); p.setAttribute('style', 'background-image: url(' + u + 'images/pending.gif) !important'); c.appendChild(p); s.setAttribute('src', u + 'js/wappalyzer.js?' + t); s.onload = function() { s = d.createElement('script'); s.setAttribute('src', u + 'js/apps.js?' + t); s.onload = function() { s = d.createElement('script'); s.setAttribute('src', u + 'js/driver.js?' + t); c.appendChild(s); }; c.appendChild(s); }; c.appendChild(s); d.body.appendChild(c); })();

Bookmarklet: Drivers @ AliasIO/Wappalyzer Wiki Please note that there is a debug output in console when using bookmarklet, which is added bonus.

I also customized wappylizer.js to show me the matched rules (hosted on koviljaca.rs)

Built With


There is also Chrome extension. It is heavily cached results in cloud.

PHP Library for detecting CMS


Stupid old CMS Detector





https://community.qualys.com/community/blindelephant BlindElephant | Penetration Testing Tools BlindElephant Web Application Fingerprinter

Wordpress Fingreprinting Sites

  1. Wordpress Theme Detector (and Plugins) - ScanWP, Detect any WP Theme with corresponding chrome extension: Scan WP - Detect Wordpress Themes and Plugins - Chrome Web Store

  2. WordPress Theme Detector - Free online tool to find a site´s theme

Chrome Extensions that do that

Samo proveri jel dobro radiš: https://premium.wpmudev.org/blog/how-to-hide-your-wordpress-version-number/?npp=b&utm_expid=3606929-81.6_x2aktJQ2qbOSnTRGna0w.1 https://wordpress.org/support/topic/wp-44-remove-json-api-and-x-pingback-from-http-headers/

It is mostly this Wappylizer rule:

"<link rel=[\"']stylesheet[\"'] [^>]+wp-(?:content|includes)",

What the env variable is for in the apps.js config? Those are JavaScript environment variables in the window scope.

So to search for wp_username variable, type in console:

for(var b in window) if(window.hasOwnProperty(b) && (new RegExp("^wp_username$")).test(b)) console.log(b);

The final solution to hide Wordpress is:

  1. Rename wp-content to only content. Done.
  2. Hide login URL, by using /run or /etk. Done.
  3. Do all the removal of signatures in HTML and headers. Done.
  4. Install the whole Wordpress in subdirectory named @. Not mandatory. See example @ technomer.rs

How to do step 1?

  1. Replace wp-content with content in database with plugin Better Search Replace
  2. Rename the folder to content
  3. Edit wp-config.php and add those lines:
define('HOST_NAME', ((strpos($_SERVER['SERVER_NAME'], 'www.') === 0) ? '' : 'www.') . $_SERVER['SERVER_NAME'] );

define('WP_HOME',    'https://' . HOST_NAME . '/');
define('WP_SITEURL', 'https://' . HOST_NAME . '/');

/* Move wp-content */

define('WP_CONTENT_FOLDERNAME', 'content');
  1. Add the line to nginx.conf: rewrite ^/wp-content/(.*) /content/$1 last; in case someone hot-linked something from our site (facebook posts) and we also don’t want to lose SEO.

  2. Dont forget to check all the nginx .conf files and replace wp-content to content.

Plugins and themes folder location should not be in the same subfolder, as this is how WPScan tries to detect one based on another. So it should be something like:

/content /design /ext/s /ext/m

  1. Replace themes/ with design/ in database with plugin Better Search Replace
  2. Rename the themes folder to design
  3. Edit wp-config.php and add those lines:

Ajde, smisli lepa imena i lepu strukturu. I napravi da se uvek automatski sve to podesi kad instaliraš WP. Nešto automatizovano (a sve to unutar @) ;)

Ustvari, theme ne moraš da pomeraš. samo dodaj svoju u novi dir. Tako se neće poojavljivati nikad u source-u i svi srećni ;)

Detecting fingerprinting with:

clear; tail log/access.log -f -n0 | grep -Ei ' 200.*wpscan'

wpscan is checking all these:

/wp-content                                   404
/content/plugins                              301
/readme.html                                  403
/wp-includes/rss-functions.php                500
/content/debug.log                            403
/%23wp-config.php%23                          404
/wp-config.php.swo                            404
/wp-config.bak                                404
/wp-config.save                               404
/wp-config.php.old                            404
/wp-config.php~                               404
/wp-config.php.save                           404
/wp-config.orig                               404
/wp-config.original                           404
/wp-config.php_bak                            404
/wp-config.php.swp                            404
/wp-config.php.bak                            404
/wp-config.old                                404
/wp-config.php.original                       404
/wp-config.php.orig                           404
/wp-config.txt                                404
/searchreplacedb2.php                         404
/wp-signup.php                                500
/content/mu-plugins/                          404
/wp-login.php?action=register                 404
/xmlrpc.php                                   403
/content/uploads/                             403
/content/themes/twentyfifteen/readme.txt      403
/content/themes/twentyfifteen/README.txt      404
/content/themes/twentyfifteen/Readme.txt      404
/content/themes/twentyfifteen/ReadMe.txt      404
/content/themes/twentyfifteen/README.TXT      404
/content/themes/twentyfifteen/readme.TXT      404
/content/themes/twentyfifteen/changelog.txt   404
/content/themes/twentyfifteen/                500
/content/themes/twentyfifteen/error_log       404

/feed/                                        200   // solved disabling RSS feeds
/feed/rdf/                                    200   // solved disabling RSS feeds
/feed/atom/                                   200   // solved disabling RSS feeds
/wp-includes/css/buttons-rtl.css              200   // ** it is poking blindly. how to solve this?
/wp-includes/js/wp-emoji-loader.min.js        200   // ** how to solve this?
/content/themes/twentyfifteen/style.css       200   // blind poking. just rename style.css (hide_my_wp does that without problems - steal from it)
  1. It is needed to fully disable RSS Feeds in Wordpress. Easy to do manually, but also by simple plugin Disable Feeds Plugin is NOT using do_feed???_ hooks?

ja bih voleo 404

function wpb_disable_feed() {
  wp_die( __('No feed available, please visit our <a href="'. get_bloginfo('url') .'">homepage</a>!') );

add_action('do_feed', 'wpb_disable_feed', 1);
add_action('do_feed_rdf', 'wpb_disable_feed', 1);
add_action('do_feed_rss', 'wpb_disable_feed', 1);
add_action('do_feed_rss2', 'wpb_disable_feed', 1);
add_action('do_feed_atom', 'wpb_disable_feed', 1);
add_action('do_feed_rss2_comments', 'wpb_disable_feed', 1);
add_action('do_feed_atom_comments', 'wpb_disable_feed', 1);

// PLUS this! I already did in clening head
//Remove feed link from header

remove_action('wp_head', 'feed_links', 2 );
remove_action('wp_head', 'feed_links_extra', 3 );

  1. Nekako wp-includes ne sme da se pojavljuje?

    /wp-includes/css/buttons-rtl.css 200 // ** it is poking blindly. how to solve this? /wp-includes/js/wp-emoji-loader.min.js 200 // ** how to solve this?

Ma jok. Dovoljno je samo blokirati ova dva u nginx-u. Ionako ih ne trebam za ništa.

E, a onda se otvara pandorina kutija i još pokinga

# If I block these, it will not detect Wordpress version
location = /wp-includes/css/buttons-rtl.css { deny all; }
location = /wp-includes/js/wp-emoji-loader.min.js { deny all; }   
location = /wp-includes/js/mediaelement/mediaelement-and-player.min.js { deny all; }
location = /wp-includes/js/tinymce/wp-tinymce.js.gz { deny all; }
location = /wp-admin/js/customize-nav-menus.min.js { deny all; }
location = /wp-admin/js/customize-controls.js { deny all; }
location = /wp-includes/js/customize-preview.js { deny all; }
location = /wp-admin/js/common.js { deny all; }
location = /wp-includes/css/admin-bar.css { deny all; }
location = /wp-includes/js/wp-ajax-response.js { deny all; }
location = /wp-includes/js/thickbox/thickbox.css { deny all; }
location = /wp-links-opml.php { deny all; }

Sve je ovo napravljeno da detektuje WP verziju: vidi fajl wp_versions.xml unutar https://github.com/wpscanteam/wpscan/blob/master/data.zip Ali mislim da se ništa neće pokrenuti ako uspem da napravim da ne detektuje WP.

Above poking is to determine Wordpress version.

How does it detect WP version? Everything is inside file _[findable.rb](Here is the file https://github.com/wpscanteam/wpscan/blob/master/lib/common/models/wp_version/findable.rb)_ It also data/wp_versions.xml to try to identify a wordpress version, does this by using client side file hashing A u tom fajlu on čita: readme.html

Finally, it still detects it is Wordpress, just it don’t know which version. And WP existance is based on theme detection, as renaming the Theme folder does the job perfectly.

  1. If I specify folder /fucker/themes/current for theme, it searches for fucker/plugins So, you MUST NOT mention themes in theme folder, but for example theme is ok! Anyway, let’s go for /skin

    So this is how it determines plugins folder.

And that is enough. No need to change theme names or anything. Just change themes folder name to something other than themes, like /design, /skin or /looks

Ok. Here you can detect:

Dissecting WPScan detection

If you don’t mention themes, plugins, wp-content anywhere in HTML, the scan will not detect Wordpress at all, in the first place.

But even Google Analytics’s JS plugins with URL’s like google-analytics.com/plugins will trick WPScan to think that site is using Wordpress.

For themes you can use register_theme_directory() function to add additional directories for WP to be aware of. I don’t think I ever seen this used in practice, so not sure if there are any complications possible.

For plugins you can define WP_PLUGIN_DIR and WP_PLUGIN_URL constants in wp-config.php.

Decision on naming folders

I prefer using singular nouns (vs plurals) as we want to hide the fact there are multiple sub-folders inside some folder.

  • wp-content is now content. Candidates were: content, app, assets

  • themes is now style. Candidated were: design, skin, look, appearance, decor, view.
    It is very important to be singular as we don’t want to reveal there are multiple styles, which indicates some kind of CMS. There are subdirectories style/core and style/extend with parent and child theme in them. Please note that original content/themes folder will still exist with all the other themes residing there.

  • plugins are now modules. Candidated were: components, extensions, addons, bits, features, sections, segments

Execute folder renaming by doing it like this:

  1. Rename /wp-content to /content
  2. Rename /content/plugins to /modules/core and /content/mu-plugins to modules/requisite
  3. Create folder /style/core and /style/extend and move parent and child theme there. Other than that, leave content/themes as is.

Themes and plugins are now surely not the same folder level.

Renaming of specific parent & child theme folders is necessary as also parent theme folder is mentioned in style of child themes. So we renamed parent’s theme folder to core and child’s theme folder to extend

Final folder placement will look like this:

/content ...
         /themes ( unused themes, must exist )

/modules /core ( plugins )
         /requisite ( mu-plugins )

/style   /core ( parent theme )
         /extend ( child theme, current )

Execution on site

(1) Search & replace from within Wordpress Admin

  1. Replace wp-content with content in database with plugin Better Search Replace.
  2. Replace content/themes with style

(2) Restructure folders

Create the new folder structue:

mv wp-content content

mkdir modules
mv content/plugins modules/core
mv content/mu-plugins modules/requisite

mkdir style
mv content/themes/<parent-theme> style/core
mv content/themes/<child-theme> style/extend

chown --reference content style modules
chmod --reference content style modules

Theme mangling

It is impossible to rename the base themes folder (content/themes), but as both parent and child themes are in subdirectories, nothing in HTML body will hint the other themes location.

To retrieve active theme directly from database, use this SQL:

SELECT * FROM etkwp_options
    WHERE option_name IN ('template', 'stylesheet', 'current_theme');

Simplest way to automate theme folder changes is to execute SQL query after moving and renaming the folders:

UPDATE etkwp_options SET option_value = 'save-up' WHERE option_name = 'current_theme';

UPDATE etkwp_options SET option_value = 'core' WHERE option_name = 'template';
UPDATE etkwp_options SET option_value = 'extend' WHERE option_name = 'stylesheet';

Just as a reminder, to reset theme values to default, use SQL:

UPDATE etkwp_options SET option_value='default'
    WHERE option_name='template' OR option_name='stylesheet' LIMIT 2;

Renaming uploads folder

Renaming uploads folder is not quite straightforward, but it is possible as explained in this article.

Anyway, it turns out we don’t need that for now, so it can reside in /content/uploads.

Renaming themes folder

There used to be, now unnecessary way of renaming theme folder, explained here

I decided to use easier and perfectly stable route and to create a new simple mu-plugin to add additional theme folder.

I’ve done this as mu-plugin because the muplugins_loaded hook is really the first hook to fire in Wordpress, as seen here

cat <<'EOF' >> modules/requisite/register_theme_directory.php
Plugin Name: Register Additional Theme Folder
Plugin URI: http://www.cvladan.com
Description: Adds an additional folder for themes
Version: 1.0.0
Author: Vladan Colovic
Author URI: http://www.cvladan.com
License: GPL2

// Do not include a trailing slash.
register_theme_directory(ABSPATH . '/style');

Setting permissions is also needed:

chown --reference . modules/requisite/register_theme_directory.php
chmod --reference . modules/requisite/register_theme_directory.php

Additional theme settings

Fix child theme code

We must search and edit the child’s theme code to set parent theme to core inside styles.css and search & replace every other occurrence of sahifa and change it to core

Fix theme menus

After renaming themes, there is a problem of “detached” menus; not assigned to their locations.

The simplest fix is to change current menu assignements inside the admin panel, by going to Appearance » Menus » Tab: Manage Locations (URL: /wp-admin/nav-menus.php?action=locations) page and select menus you need.

You could also use the following SQL query:

SELECT * FROM etkwp_terms AS t
    LEFT JOIN etkwp_term_taxonomy AS tt ON tt.term_id = t.term_id
    WHERE tt.taxonomy = 'nav_menu';

…or more simple, alternative query for the same thing:

SELECT * FROM etkwp_options WHERE option_name LIKE 'theme_mods_%'

After that, renaming values theme_mods_etaktiker with theme_mods_extend should do the same thing as doing it inside Wordpress admin panel. To know more about menu representation in database tables read this very informative answer

WordPress as a Git submodule in /@/ or app?

Great analysis and some interesting solutions: prevent WPScan from scanning

// Sve ove nabode i izračuna hash wp-admin/js/customize-nav-menus.min.js wp-admin/js/customize-controls.js wp-admin/js/common.js wp-includes/css/buttons-rtl.css wp-includes/js/wp-emoji-loader.min.js wp-includes/js/tinymce/wp-tinymce.js.gz wp-includes/js/customize-preview.js wp-includes/css/admin-bar.css wp-includes/js/wp-ajax-response.js wp-includes/js/thickbox/thickbox.css

// Ovih definitivno nema nigde više wp-admin/js/wp-fullscreen.js wp-includes/js/plupload/plupload.js wp-includes/js/tinymce/plugins/wpeditimage/editor_plugin.js wp-includes/js/tinymce/themes/advanced/js/image.js wp-includes/js/tinymce/themes/advanced/js/link.js wp-includes/js/wp-ajax.js wp-layout.css layout2b.css

// Ove sam ili već blokirao ili on nema pojma gde je folder readme.html $wp-content$/themes/twentyten/style.css $wp-plugins$/akismet/readme.txt $wp-content$/themes/default/style.css

The best way to solve this is:

# If I block these, it will not detect Wordpress version
# @see: https://codex.wordpress.org/Nginx

location = /wp-admin/js/common.js { try_files /dummy_nonexistant_file @login_check; }

location @login_check {

  set $is_user_logged_in 0;

  # Don't use the cache for logged in users or recent commenters
  if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|wordpress_no_cache|wordpress_logged_in") {
    set $is_user_logged_in 1;

  if ($is_user_logged_in ~ 0) {
     return 404;

For advanced fingerprinting, solve it like these:

  1. If it inside admin area, use cookie check if logged in
  2. If it is frontend, use self-referrer check I hope this one will never be used ;)

At the end, only the /wp-admin/admin-ajax.php has been left problematic, but it seems nobody is fingerprinting it for now.

Fixing admin-ajax.php?

I finally found a perfect solution to my problem of admin-ajax visibility on frontend:

  1. using admin_url filter hook ensures that admin-ajax.php or /wp-admin will never be mentioned on frontend HTML

  2. admin-ajax.php is returning 404 by default anyway in Wordpress, so blind fingerprinting won’t work. Beautiful!

In code:

add_filter( 'admin_url', function( $url ) {

  if (preg_match( '/\/admin-ajax\.php$/', $url )) {
    $url = '/request';

  return $url;
}, 10, 1 );

In nginx:

rewrite ^/request$ /wp-admin/admin-ajax.php last;

Basic idea can be examined here: Restrict access to WP-admin by IP in Nginx. There is always a more advanced example: Change Admin URL

Hotlinking prevention?

I already protected most of the stuff inside /wp-includes and /wp- admin, by using technique to detect logged users by cookie in nginx, but there is still a fingerprinting problem of admin-ajax.php as it has to be available on non-logged users also.

My idea was not to prevent images from hotlinking, but to ensure that any request to admin-ajax.php is done from the same site, and not externally for fingerprinting purposes.

location = /wp-admin/admin-ajax.php { try_files /dummy_nonexistant_file @referer_check; } 

location @referer_check {

  valid_referers server_names;
  if ($invalid_referer) {
    return 404;

Note: valid_referers directive doesn’t support variables

nginx - Image hotlink protection using rewrite - nikhil’s blog how to implement hotlinking prevention? - Stack Overflow

At the end

Don’t forget to refresh permalinks.

date 23. Dec 2016 | modified 20. Jan 2023
filename: WordPress » Security » Hide Wordpress