Como personalizar la página de inicio de sesión de WordPress sin plugins

Como personalizar la página de inicio de sesión de WordPress sin plugins

Última verificación: 14 de octubre de 2026
19 min de lectura
Guía
Auditor de seguridad
Desarrollador full-stack

La pantalla de login predeterminada de WordPress (wp-login.php) es funcional y reconocible. En un proyecto de cliente sigue leyéndose como «CMS de serie», no como su producto. Poner marca blanca en el login es un cambio pequeño con mucho acabado: logo, colores, errores más discretos y CSS que solo carga en esa URL.

Esta guía usa solo hooks del núcleo, contrastados con el código de WordPress 7.1. Sin plugin de branding. Pon el código en un tema hijo o en un plugin pequeño del sitio (o must-use) para que una actualización del tema padre no lo borre. Si el sitio ya tiene historial de exposición, el endurecimiento que va más allá de lo cosmético encaja mejor tras revisar el entorno; puedes empezar por contacto cuando haga falta encajar marca y endurecimiento en el mismo trabajo.

#Por qué personalizar el login de WordPress

Editores y clientes llegan a wp-login.php más a menudo que a muchas páginas de marketing. Un logo de WordPress junto a los colores de su marca rompe la confianza antes de llegar al escritorio. Las agencias también usan un login con marca como señal suave de que la instalación se cuidó, no se dejó en valores por defecto.

Personalizar no es seguridad por sí sola. Cambiar el logo no detiene el credential stuffing. Combina la marca con texto de error genérico, límites de intentos (host o plugin) y 2FA en cuentas privilegiadas.

El briefing típico del cliente: «Que parezca nuestra app». Eso suele significar logo, fondo, color del botón principal y enlaces de pie que apuntan a su dominio. No significa reescribir la autenticación. Mantén el alcance honesto en el presupuesto para que nadie espere un SSO a partir de un ticket de CSS.

En redes multisite a menudo se quiere un login con marca para todos los sitios. Pon el CSS compartido en un must-use o en un plugin de red.

#Qué carga WordPress en wp-login.php

Antes de cambiar el logo conviene saber qué hace el núcleo cuando se pide wp-login.php:

  1. Carga wp-admin/css/login.css y los estilos del escritorio de los que depende (botones, formularios).
  2. Ejecuta login_enqueue_scripts, donde añades tu CSS y JS, igual que wp_enqueue_scripts en el front.
  3. Pinta la cabecera con el enlace del logo (por defecto a wordpress.org), el formulario, los enlaces de contraseña olvidada y registro, y el pie.
  4. Ante un error de validación añade el script del temblor y muestra el mensaje del filtro login_errors.
  5. Los filtros login_message y login_messages permiten colocar HTML encima del formulario (aviso tras cerrar sesión, nota de mantenimiento, banner de SSO).

La consecuencia clave: la página de login no pasa por el pipeline del tema del front. header.php, las plantillas de un tema de bloques y la mayoría de callbacks de wp_enqueue_scripts no se ejecutan ahí. Por eso existe una acción de encolado propia y por eso los estilos del login necesitan su propio archivo. Las reglas que solo viven en el style.css del tema nunca llegan a wp-login.php.

La segunda consecuencia: una actualización de WordPress puede cambiar selectores, el orden de los scripts o el markup del formulario (campos extra de un plugin de 2FA, por ejemplo). La marca construida con hooks sobrevive a la actualización; un wp-login.php parcheado en el núcleo, no.

#Cambiar el logo del login de WordPress sin plugin

Engancha login_enqueue_scripts y sobrescribe la imagen de fondo en #login h1 a.

function wppoland_login_logo() {
	?>
	<style type="text/css">
		#login h1 a,
		.login h1 a {
			background-image: url(<?php echo esc_url( get_stylesheet_directory_uri() . '/images/cliente-logo.svg' ); ?>);
			height: 65px;
			width: 320px;
			background-size: contain;
			background-repeat: no-repeat;
			padding-bottom: 30px;
		}
	</style>
	<?php
}
add_action( 'login_enqueue_scripts', 'wppoland_login_logo' );

Usa esc_url en la ruta de la imagen. Prefiere SVG o un PNG listo para retina; un JPEG grande se ve blando en pantallas HiDPI. Guarda el archivo en el tema hijo para que los deploys sean predecibles.

Por defecto el logo enlaza a wordpress.org. Apúntalo al inicio del sitio:

function wppoland_login_logo_url() {
	return home_url( '/' );
}
add_filter( 'login_headerurl', 'wppoland_login_logo_url' );

Opcionalmente filtra login_headertext para que el texto accesible del enlace coincida con el nombre del cliente y no con el texto por defecto de WordPress. Sustituyó a login_headertitle, obsoleto desde WordPress 5.2; los snippets antiguos todavía lo usan.

function wppoland_login_logo_title() {
	return get_bloginfo( 'name' );
}
add_filter( 'login_headertext', 'wppoland_login_logo_title' );

En instalaciones con un dominio aparte para el escritorio (cms.cliente.es junto a www.cliente.es), home_url() sigue apuntando al sitio público, que suele ser lo deseado. Si el logo debe llevar a documentación interna o al proveedor de identidad, devuelve una URL HTTPS completa y pruébala en una ventana privada.

#Añadir un mensaje encima del formulario de login de WordPress

El filtro login_message coloca HTML encima del formulario. Usos habituales en producción:

  • Un aviso de mantenimiento («Esta noche no se puede entrar fuera de la VPN»).
  • Una indicación para editores («Usa la cuenta de empresa, no una personal»).
  • Un banner tras cerrar sesión o restablecer la contraseña (el núcleo también añade ahí sus propias clases de mensaje).
  • Un enlace a SSO cuando el proveedor de identidad convive con las contraseñas clásicas.
/**
 * Mensaje encima del formulario de login.
 *
 * @param string $message HTML existente.
 * @return string
 */
function wppoland_login_message( $message ) {
	$notice = '<p class="message">' . esc_html__(
		'Cuentas solo para el equipo. Si sospechas una fuga de contraseña, avisa a IT.',
		'your-textdomain'
	) . '</p>';

	return $notice . $message;
}
add_filter( 'login_message', 'wppoland_login_message' );

Notas de implementación:

  • Escapa el texto (esc_html__), o construye el markup con wp_kses_post si necesita un enlace.
  • No sustituyas $message a ciegas. El núcleo coloca ahí sus propios avisos (después de loggedout=true, por ejemplo). Antepón tu texto o filtra de forma condicional según la petición.
  • La clase .message ya tiene estilos en login.css. Tu style-login.css debe respetarla (color del texto, borde izquierdo), o el banner desaparece contra el fondo.

Para textos que dependen de la acción (login, lostpassword, rp), comprueba $_GET['action'] en lugar de mostrar la misma nota en la recuperación de contraseña y en el paso de 2FA.

#Añadir CSS personalizado al login de WordPress

No vuelques reglas de login en el style.css público. Encola un archivo dedicado en el mismo hook:

function wppoland_login_stylesheet() {
	wp_enqueue_style(
		'wppoland-custom-login',
		get_stylesheet_directory_uri() . '/style-login.css',
		array(),
		wp_get_theme()->get( 'Version' )
	);
}
add_action( 'login_enqueue_scripts', 'wppoland_login_stylesheet' );

Ejemplo de style-login.css:

body.login {
	background-color: #0d1117;
	display: flex;
	align-items: center;
	justify-content: center;
}

.login form {
	background: #161b22;
	border: 1px solid #30363d;
	box-shadow: none;
	border-radius: 8px;
}

.login label {
	color: #c9d1d9;
}

.wp-core-ui .button-primary {
	background: #238636;
	border-color: rgba(27, 31, 35, 0.15);
}

.login #backtoblog a,
.login #nav a {
	color: #c9d1d9;
}

Ajusta el contraste a WCAG cuando los editores lo necesiten: gris claro sobre gris oscuro falla para algunos usuarios. Prueba los outlines de foco en usuario, contraseña y envío; quitar outlines por «diseño limpio» es una regresión.

Los paquetes de idioma y las locales RTL siguen usando los mismos hooks. Evita cadenas en inglés fijas en propiedades CSS content.

Las imágenes de fondo en body.login deben estar comprimidas. Prefiere un color de marca sólido o un patrón sutil por debajo de 100 KB. Si el manual exige un formulario claro sobre fondo claro, sube el contraste del borde en .login form para que la tarjeta no se funda con la página.

#Cambiar el mensaje de error del login de WordPress

Un fallo de contraseña para un usuario existente históricamente producía un mensaje que confirmaba el nombre de usuario. Eso ayuda a enumerar cuentas antes de forzar contraseñas.

function wppoland_login_errors() {
	return __( 'Credenciales no válidas.', 'your-textdomain' );
}
add_filter( 'login_errors', 'wppoland_login_errors' );

Mantén el texto aburrido e idéntico para usuario desconocido y contraseña incorrecta. Esto es ofuscación frente a la enumeración, no un sustituto de:

  1. Prohibir contraseñas de administración débiles.
  2. Limitar intentos de login en la aplicación o en el edge.
  3. Activar 2FA para administradores.
  4. No usar admin como nombre de usuario en instalaciones nuevas.

Los flujos de contraseña perdida tienen sus propios mensajes. Revísalos si personalizas registro o plugins de membresía que comparten el CSS de login.

Algunos plugins de membresía sustituyen wp-login.php por un formulario en el front. En ese caso estos hooks no se disparan. Aplica las mismas reglas en los hooks del plugin, o estiliza sus plantillas parciales. Confirma qué URL usan realmente los editores antes de dar el trabajo por cerrado.

XML-RPC y el endpoint REST de usuarios son superficies de ataque aparte. Marcar wp-login.php no hace nada por ellas. Desactiva o restringe XML-RPC si no lo necesitas, y mantén las contraseñas de aplicación bajo política si el sitio las usa.

#Desactivar la animación shake del login de WordPress

Los logins fallidos activan un temblor vía wp_shake_js. Algunas guías de marca lo tratan como ruido.

El snippet que siguen copiando casi todos los tutoriales, remove_action( 'login_head', 'wp_shake_js', 12 ), no hace nada desde WordPress 5.4. El núcleo engancha ahora wp_shake_js a login_footer, y solo de forma condicional, dentro de login_header(). Se ve en wp-login.php del repositorio wordpress-develop; la rama 5.3 todavía usaba login_head. El método estable es el filtro shake_error_codes, disponible desde la 3.0: una lista vacía de códigos de error significa que no hay animación.

add_filter( 'shake_error_codes', '__return_empty_array' );

El filtro es API pública, así que no depende de la prioridad ni del hook desde el que el núcleo imprima el script. Documenta la intención en un comentario de una línea encima.

#Cómo personalizar 2FA y passkeys en wp-login.php

Construir 2FA completo dentro de functions.php es el proyecto equivocado. Usa un plugin mantenido (por ejemplo el plugin oficial Two Factor o la oferta de tu hosting) y estiliza los campos extra para que encajen con el formulario de marca.

El plugin Two Factor del directorio de WordPress.org (versión 0.17.0, más de 100.000 instalaciones activas según la API del directorio en octubre de 2026) emite estas clases en el segundo paso del login. Están sacadas del código de la 0.17.0:

  • .two-factor-prompt para el párrafo de instrucciones,
  • input.authcode (con id="authcode") para el código TOTP, el código por correo y el código de respaldo; el campo lleva inputmode="numeric" y autocomplete="one-time-code", así que no sobrescribas esos atributos con un script, o el móvil deja de ofrecer el código del SMS o del gestor de contraseñas,
  • .backup-methods-wrap para los enlaces a métodos alternativos,
  • .two-factor-email-resend para el botón que reenvía el código por correo.
.login .two-factor-prompt {
	color: #c9d1d9;
}

.login input.authcode {
	letter-spacing: 0.2em;
	text-align: center;
}

.login .backup-methods-wrap a {
	color: #c9d1d9;
}

Los tutoriales antiguos usan selectores como .backup-methods o .two-factor-methods. El código actual del plugin no tiene ninguno de los dos, así que esas reglas nunca aplican. Carga las reglas que sí funcionan en el mismo style-login.css. Tras cada actualización del plugin, vuelve a revisar el segundo paso del login en staging, porque los nombres de clase se mueven. Otros plugins de 2FA y passkeys tienen su propio markup: lee sus selectores en su código, no en esta guía.

La UX de passkeys y WebAuthn varía según el plugin. En el lado de marca tu trabajo es contraste, espaciado y no recortar el prompt de la llave de seguridad con overflow: hidden en .login form.

#Tema hijo o plugin para personalizar el login de WordPress

UbicaciónProsContras
Tema hijoViaja con assets del tema y la ruta del logoSe pierde si alguien cambia de tema
Plugin específico del sitioSobrevive a cambios de temaLa URL del logo no debe hardcodear la ruta del tema antiguo
Must-useDifícil de desactivar por accidenteNecesita acceso de deploy; no ideal para todos los clientes

Las agencias que entregan el sitio suelen preferir un plugin pequeño con el nombre del cliente y el logo en assets/. Así la marca del login se mantiene cuando marketing instala después un tema de bloques nuevo.

Una versión como plugin must-use con el encolado y los dos filtros del logo:

<?php
/**
 * Plugin Name: Login Branding
 * Description: Marca blanca de wp-login.php sin depender del tema activo.
 */

defined( 'ABSPATH' ) || exit;

add_action( 'login_enqueue_scripts', function () {
	wp_enqueue_style(
		'site-login',
		plugin_dir_url( __FILE__ ) . 'assets/style-login.css',
		array(),
		'2026.10.14'
	);
} );

add_filter( 'login_headerurl', function () {
	return home_url( '/' );
} );

add_filter( 'login_headertext', function () {
	return get_bloginfo( 'name' );
} );

Una regla para el briefing: si la marca del login forma parte del producto del cliente (colores de marca, logo de campaña), usa el tema hijo. Si es un estándar de la agencia en muchas instalaciones (el mismo logo de agencia en todos los staging, errores genéricos, sin temblor), usa un plugin must-use o un paquete Composer compartido que se despliegue en todos los entornos.

No guardes este código en un plugin de snippets que almacena PHP en la base de datos. Sin control de versiones se pierde o se duplica al pasar de staging a producción.

#Errores al personalizar el login de WordPress

  1. No edites wp-login.php en el núcleo. Las actualizaciones lo sobrescriben.
  2. No encoles bundles frontend pesados en el login; mantén el CSS ligero.
  3. No ocultes la URL de login y llames a eso «seguridad» mientras dejas admin / password123.
  4. No apiles tres plugins de personalización de login encima de estos hooks.
  5. No pongas secretos ni claves API en el CSS de login.

Cambiar el slug de login (vía un plugin dedicado) es una decisión aparte. Puede recortar bots casuales; también rompe marcadores y algunos checklists de deploy. Marca y cambio de slug son independientes.

Si renombras la ruta de login, documenta la nueva URL en la entrega y en el gestor de contraseñas del cliente.

#Cómo personalizar el login de WooCommerce y plugins de membresía

Las páginas de cuenta de WooCommerce y los bloques de login en checkout usan otro markup. El CSS de login no restiliza my-account. O aceptas los formularios de Woo o encolas una hoja aparte. Mezclar ambos en style-login.css crea reglas muertas.

La misma separación aplica a LearnDash, MemberPress y similares: identifica la URL de entrada real. Un error habitual es pulir wp-login.php mientras los clientes solo ven un modal front con el logo de WordPress.

#Cómo revisar el login tras actualizar WordPress

Las versiones mayores de WordPress a veces retocan el markup o el CSS por defecto del login. Tras actualizar el núcleo en staging:

  1. Diff el HTML de login si algo se ve mal.
  2. Vuelve a probar el tamaño del logo; nuevas alturas por defecto pueden recortar SVG.
  3. Si desactivaste el temblor, confirma que el filtro shake_error_codes sigue aplicándose.
  4. Revisa los campos de 2FA si ese plugin se actualizó en la misma ventana.

Fija la versión de la hoja de estilos a la del tema o del plugin para que el navegador no conserve un style-login.css antiguo tras un arreglo de contraste.

#Qué hooks de login tiene WordPress y desde qué versión

La tabla sale de las etiquetas @since de src/wp-login.php en el repositorio wordpress-develop (octubre de 2026, versión actual WordPress 7.1.3). Sirve cuando un cliente mantiene una instalación antigua o cuando un snippet de un foro usa un hook que todavía no existe o que ya está obsoleto.

HookTipoDesdePara qué sirve
login_headerurlfiltro2.1.0URL del enlace del logo
login_messagefiltro2.1.0HTML encima del formulario
login_errorsfiltro2.1.0texto de los errores
login_messagesfiltro2.5.0avisos informativos
login_redirectfiltro3.0.0destino de la redirección tras el login
shake_error_codesfiltro3.0.0códigos de error que hacen temblar el formulario
login_enqueue_scriptsacción3.1.0CSS y JS de la pantalla de login
login_footeracción3.1.0salida en el pie
login_initacción3.2.0inicio del procesamiento de la petición
login_body_classfiltro3.5.0clases del elemento body
login_headeracción4.6.0salida justo tras abrir el body
login_titlefiltro4.9.0contenido de la etiqueta title
login_link_separatorfiltro4.9.0separador de los enlaces bajo el formulario
login_headertextfiltro5.2.0texto del enlace del logo
login_site_html_linkfiltro5.7.0enlace de vuelta al sitio en el pie
login_display_language_dropdownfiltro5.9.0si se muestra el selector de idioma
login_language_dropdown_argsfiltro5.9.0argumentos de la lista de idiomas

login_headertitle (desde 2.1.0) está marcado como obsoleto desde la 5.2.0 y pasa por apply_filters_deprecated, así que con WP_DEBUG activo deja un aviso en el log. Si el tema de un cliente todavía lo usa, cámbialo por login_headertext.

#Cambiar el título, la clase del body y el selector de idioma en wp-login.php

Cuatro filtros de la tabla resuelven peticiones pequeñas del briefing sin tocar CSS:

// Título de pestaña más corto: nombre de la pantalla y nombre del sitio.
add_filter( 'login_title', function ( $login_title, $title ) {
	return $title . ' | ' . get_bloginfo( 'name', 'display' );
}, 10, 2 );

// Clase propia en el body para que las reglas del login no choquen con otras hojas.
add_filter( 'login_body_class', function ( $classes ) {
	$classes[] = 'site-login';
	return $classes;
} );

// El enlace del pie lleva a una página de ayuda para editores en lugar del inicio.
add_filter( 'login_site_html_link', function () {
	return sprintf(
		'<a href="%s">%s</a>',
		esc_url( home_url( '/ayuda/' ) ),
		esc_html__( 'Ayuda para editores', 'your-textdomain' )
	);
} );

// Sin lista de idiomas bajo el formulario.
add_filter( 'login_display_language_dropdown', '__return_false' );

Detalles que solo aparecen en el código del núcleo:

  • El título por defecto combina el nombre de la pantalla, el nombre del sitio y la palabra WordPress al final. En modo de recuperación el núcleo añade un prefijo antes de aplicar el filtro. Un callback que construye el título solo con $title pierde ese prefijo, y el administrador deja de ver en la pestaña que el sitio está en modo de recuperación. Si eso importa, modifica $login_title en lugar de sustituirlo.
  • La lista de idiomas solo aparece cuando hay al menos un paquete de idioma adicional instalado, y nunca en el modal de reconexión del escritorio. Ocultarla no cambia el idioma de la cuenta: tras el login se aplica el ajuste del perfil.
  • login_site_html_link sustituye el elemento <a> completo, así que escapar la URL y el texto es cosa tuya.
  • El enlace a la política de privacidad bajo el formulario sale de the_privacy_policy_link() y aparece cuando hay una página de privacidad configurada. No lo ocultes con CSS en sitios que recogen datos con un formulario de registro.

#Qué cabeceras de seguridad envía WordPress en wp-login.php

Antes de añadir tus propias cabeceras o meta etiquetas a la pantalla de login, revisa lo que el núcleo ya hace. Según default-filters.php y wp-login.php:

  • send_frame_options_header() en login_init (desde 3.1.3) envía X-Frame-Options: SAMEORIGIN, y desde WordPress 6.9 la misma función añade Content-Security-Policy: frame-ancestors 'self'. La pantalla de login no se puede incrustar en un iframe de otro dominio.
  • wp_admin_headers() en login_init (desde 4.9) envía Referrer-Policy: strict-origin-when-cross-origin; el filtro admin_referrer_policy cambia el valor.
  • wp_robots_sensitive_page() (desde 5.7) añade noindex y noarchive al meta robots, que imprime wp_robots en login_head.
  • wp_strict_cross_origin_referrer() (desde 5.7) añade una meta etiqueta referrer con la misma política.
  • wp_site_icon en login_head imprime el favicon del ajuste Icono del sitio.

En la práctica, un snippet aparte que imprime un meta robots noindex o un enlace al favicon en login_head solo duplica etiquetas. No añadas X-XSS-Protection. MDN la describe como no estándar y obsoleta, advierte que puede crear vulnerabilidades XSS en sitios por lo demás seguros y recomienda Content-Security-Policy. Las cabeceras de todo el sitio (HSTS, CSP) van en la configuración del servidor o de la CDN, no en PHP sobre login_init, para que cubran también wp-admin y REST.

#Cómo probar el login personalizado de WordPress

  1. Abre /wp-login.php sin sesión; confirma logo, colores y enlace al inicio.
  2. Envía una contraseña incorrecta; confirma el error genérico y la ausencia de shake si lo quitaste.
  3. Confirma que «¿Olvidaste la contraseña?» sigue funcionando y que llegan los correos.
  4. Con 2FA activo, completa un login de administrador en staging.
  5. Cambia a un segundo idioma si el sitio es multilingüe y revisa el layout.
  6. Entra desde un viewport móvil; el centrado con flex a veces colapsa el formulario en pantallas bajas.

#Resumen

Marca el logo y la URL del encabezado, encola CSS solo para login, devuelve errores genéricos y, si quieres, quita el shake. Mantén el código fuera del tema padre. Trata el 2FA como un control real y estiliza su UI; trata el CSS del logo como acabado.

Entrega al cliente una nota de una página: dónde vive el código, cómo sustituir el archivo del logo y cuál es la URL canónica para el personal. Esa nota evita que el siguiente freelance instale un plugin de login redundante seis meses después.

Si la instalación ya muestra indicios de abuso, o necesitas revisar el endurecimiento del login junto con plugins y hosting, empieza por contacto en lugar de apilar otro plugin cosmético sobre un sitio comprometido.

Siguiente paso

Transforma el artículo en una implementación real

Este bloque refuerza el enlazado interno y lleva al lector al siguiente paso más útil dentro de la arquitectura del sitio.

Cluster relacionado

Explora otros servicios WordPress y base de conocimiento

Refuerza tu negocio con soporte técnico profesional en áreas clave del ecosistema WordPress.

¿Cómo cambiar el logo de login de WordPress sin un plugin?#
Usa el hook login_enqueue_scripts, genera CSS para la pantalla de login y reemplaza el logo predeterminado con tu propia imagen del tema o tema hijo.
¿Se puede personalizar wp-login.php y mejorar la seguridad al mismo tiempo?#
Sí. Puedes personalizar la página, cargar CSS dedicado, devolver errores de login genéricos y eliminar comportamientos de UI molestos sin cambiar la experiencia del panel de administración.
¿Dónde debe vivir el código de personalización del login de WordPress?#
Usa un tema hijo o un plugin pequeño específico del sitio para que los cambios sobrevivan a las actualizaciones del tema y permanezcan aislados del estilo frontend no relacionado.
¿Ocultar errores de login detiene los ataques de fuerza bruta?#
No. Los errores genéricos reducen la enumeración de usuarios. Sigues necesitando limitar intentos, credenciales fuertes y, preferiblemente, 2FA o passkeys en cuentas de administración.
¿Un plugin de CSS para login chocará con este código?#
A menudo sí. Dos fuentes estilizando #login h1 a se pisan entre sí. Elige hooks en código o un solo plugin, no ambos, y prueba tras cada actualización del núcleo.

¿Necesitas un FAQ adaptado a tu sector y mercado? Preparamos una versión alineada con tus objetivos de negocio.

Hablemos

Artículos Relacionados