How to Hide That Your Site is Using  WordPress

I get a lot emails asking how to hide that a site is using WordPress. I thought I would walk through some of the functions I have in my theme to accomplish this. Most of these methods are taken directly from the Roots theme by Ben Ward. If you haven’t already, I highly recommend checking it out.

URL Rewrites

First you’ll need to remove any links containing /wp-content/, /themes/, and /plugins/. The following functions will rewrite your WordPress URL’s, mimicking the HTML5 Boilerplate URL structure.

/**
 * URL Rewriting
 *
 * Rewrite:
 *  /wp-content/themes/themename/assets/css/ to /assets/css/
 *  /wp-content/themes/themename/assets/js/ to /assets/js/
 *  /wp-content/themes/themename/assets/img/ to /assets/img/
 *  /wp-content/plugins/ to /plugins/
 */
function nowp_add_rewrites($content) {
    global $wp_rewrite;
    $nowp_new_non_wp_rules = array(
        'assets/(.*)' => THEME_PATH . '/assets/$1',
        'plugins/(.*)'   => RELATIVE_PLUGIN_PATH . '/$1'
    );
    $wp_rewrite->non_wp_rules = array_merge($wp_rewrite->non_wp_rules, $nowp_new_non_wp_rules);
    return $content;
}

function nowp_clean_urls($content) {
    if (strpos($content, RELATIVE_PLUGIN_PATH) > 0) {
        return str_replace('/' . RELATIVE_PLUGIN_PATH,  '/plugins', $content);
    } else {
        return str_replace('/' . THEME_PATH, '', $content);
    }
}

// No rewrites for multisite or child themes
if ( !is_multisite() && !is_child_theme() ) {
    add_action('generate_rewrite_rules', 'nowp_add_rewrites');
    if ( !is_admin() ) {
        $tags = array(
            'plugins_url',
            'bloginfo',
            'stylesheet_directory_uri',
            'template_directory_uri',
            'script_loader_src',
            'style_loader_src'
        );
        add_filters($tags, 'nowp_clean_urls');
    }
}

This setup assumes that you have an /assets/ directory in your theme folder. If you’re using apache, WordPress will automatically handle the necessary .htaccess rules for you. If you’re using nginx, you’ll need to add rewrite rules in your config files to handle references to /assets/ and /plugins/.

Nginx Rewrite Rules

location ~ ^/assets/(img|js|css|fonts)/(.*)$ {
  try_files $uri $uri/ /wp-content/themes/YOURTHEME/$1/$2;
}
location ~ ^/plugins/(.*)$ {
  try_files $uri $uri/ /wp-content/plugins/$1;
}

These rewrites reference /wp-content/ directly by name, which isn’t so ideal. If you’ve changed the WP_CONTENT_URL or WP_CONTENT_DIR constants then you may run into conflicts. Just make sure that the name of your wp-content folder is properly represented here.

You’ll also want to use relative links wherever possible. WordPress likes to use absolute URLs for everything, this bit of code will clean that up.

/**
 * Relative URLs
 *
 * Inspired by Roots.io
 */
function nowp_root_relative_url($input) {
    preg_match('|https?://([^/]+)(/.*)|i', $input, $matches);

    if (isset($matches[1]) && isset($matches[2]) && $matches[1] === $_SERVER['SERVER_NAME']) {
        return wp_make_link_relative($input);
    } else {
        return $input;
    }
}
function nowp_enable_root_relative_urls() {
    return !( is_admin() || in_array($GLOBALS['pagenow'], array('wp-login.php', 'wp-register.php')) );
}
$root_rel_filters = array(
    'bloginfo_url',
    'the_permalink',
    'wp_list_pages',
    'wp_list_categories',
    'the_content_more_link',
    'the_tags',
    'get_pagenum_link',
    'get_comment_link',
    'month_link',
    'day_link',
    'year_link',
    'tag_link',
    'the_author_posts_link',
    'script_loader_src',
    'style_loader_src'
);
add_filters($root_rel_filters, 'nowp_root_relative_url');

Cleanup The Head

WordPress adds a lot to the <head> section of a theme’s HTML. This is an immediate give away that a site is powered by WordPress. For example, you probably have something like this in your source code right now.

<link rel="alternate" type="application/rss+xml" title="Example Blog &raquo; Feed" href="https://example.com/feed/" /> 
<link rel="alternate" type="application/rss+xml" title="Example Blog &raquo; Comments Feed" href="https://example.com/comments/feed/" />    
<link rel="EditURI" type="application/rsd+xml" title="RSD" href="https://example.com/xmlrpc.php?rsd" /> 
<link rel="wlwmanifest" type="application/wlwmanifest+xml" href="https://example.com/wp-includes/wlwmanifest.xml" /> 
<link rel='index' title='Example Blog' href='https://example.com/' /> 
<meta name="generator" content="WordPress 3.1-alpha" /> 

Let’s clean this up by removing the actions before the <head> section of your theme is loaded.

/**
 * Clean up wp_head()
 *
 * Remove unnecessary <link>'s
 * Remove inline CSS used by Recent Comments widget
 * Remove inline CSS used by posts with galleries
 * Remove self-closing tag and change ''s to "'s on rel_canonical()
 */
function nowp_head_cleanup() {
    // Remove junk from head
    remove_action('wp_head', 'rsd_link');
    remove_action('wp_head', 'wp_generator');
    remove_action('wp_head', 'feed_links', 2);
    remove_action('wp_head', 'index_rel_link');
    remove_action('wp_head', 'wlwmanifest_link');
    remove_action('wp_head', 'feed_links_extra', 3);
    remove_action('wp_head', 'start_post_rel_link', 10, 0);
    remove_action('wp_head', 'parent_post_rel_link', 10, 0);
    remove_action('wp_head', 'adjacent_posts_rel_link', 10, 0);
    remove_action('wp_head', 'adjacent_posts_rel_link_wp_head', 10, 0);
    remove_action('wp_head', 'wp_shortlink_wp_head', 10, 0);
    remove_action('wp_head', 'feed_links', 2);
    remove_action('wp_head', 'feed_links_extra', 3);

    global $wp_widget_factory;
    remove_action('wp_head', array($wp_widget_factory->widgets['WP_Widget_Recent_Comments'], 'recent_comments_style'));

    if (!class_exists('WPSEO_Frontend')) {
        remove_action('wp_head', 'rel_canonical');
        add_action('wp_head', 'nowp_rel_canonical');
    }
}
function nowp_rel_canonical() {
    global $wp_the_query;

    if (!is_singular()) {
        return;
    }

    if (!$id = $wp_the_query->get_queried_object_id()) {
        return;
    }

    $link = get_permalink($id);
    echo "\t<link rel=\"canonical\" href=\"$link\">\n";
}
add_action('init', 'nowp_head_cleanup');


/**
 * Remove the WordPress version
 */
add_filter('the_generator', '__return_false');


/**
 * Clean up language_attributes() used in <html> tag
 *
 * Change lang="en-US" to lang="en"
 * Remove dir="ltr"
 */
function nowp_language_attributes() {
    $attributes = array();
    $output = '';

    if (function_exists('is_rtl')) {
        if (is_rtl() == 'rtl') {
            $attributes[] = 'dir="rtl"';
        }
    }

    $lang = get_bloginfo('language');

    if ($lang && $lang !== 'en-US') {
        $attributes[] = "lang=\"$lang\"";
    } else {
        $attributes[] = 'lang="en"';
    }

    $output = implode(' ', $attributes);
    $output = apply_filters('nowp_language_attributes', $output);

    return $output;
}
add_filter('language_attributes', 'nowp_language_attributes');

That should do it!

This combination of functions should hide the fact that your site is running WordPress from 97% of people that are snooping around your source code. Of course, there’s no a guarantee, but it’s admittedly much better than an out of the box install.

Not only does this look better from a source code perspective, it can also help with security. Bots that creep around the web looking to do malicious attacks will hunt for flags to spot a WordPress site. If they’re not there, they may skip over your site entirely.

Related Articles

Meet the Author

Kevin Leary, WordPress Consultant

I'm a custom WordPress web developer and analytics consultant in Boston, MA with 17 years of experience building websites and applications. View a portfolio of my work or request an estimate for your next project.