You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Ansible自动化部署WordPress后无法进入管理仪表盘问题求助

WordPress Ansible自动化部署后无法登录管理仪表盘

我基于Ubuntu官方指南编写了Ansible Playbook,在Linux虚拟机上自动化部署WordPress。执行Playbook后,访问http://localhost:80能正常进入WordPress安装页面,填写信息后显示安装成功,但点击登录按钮后始终停留在登录页面,没有任何错误提示。

更新1:如果跳过WordPress盐值/密钥生成的步骤,部署完成后可以正常登录进入管理仪表盘。


相关代码与配置

Ansible Playbook(SetupWordpress.yaml)

- name: "Installing wordpress dependencies"
  hosts: all
  become: True
  gather_facts: True
  vars:
    get_installer: 'curl -sS https://getcomposer.org/installer -o /tmp/composer-setup.php || /bin/true'
    get_signature: 'curl -sS https://composer.github.io/installer.sig'
  tasks:
        - name: "Update repository"
          apt: 
              update_cache: "yes"
        - name: "Installing requirements"
          apt:
            name:
                 - "curl"
                 - "php"
                 - "php-cli"
                 - "gnupg"
                 - "unzip"
                 - "mysql-server"
                 - "php-fpm"
                 - "php-mysql"
                 - "apache2"
                 - "ghostscript"
                 - "libapache2-mod-php"
                 - "php-bcmath"
                 - "php-curl"
                 - "php-imagick"
                 - "php-intl"
                 - "php-json"
                 - "php-mbstring"
                 - "php-xml"
                 - "php-zip"
            state: present
        - name: Populate service facts
          ansible.builtin.service_facts:
        - name: Print service facts
          ansible.builtin.debug:
            var: ansible_facts.services
        - name: "stopping nginx if running"
          service:
            name: nginx
            state: stopped
          when: "'nginx' in ansible_facts.services"
        - name: "remove nginx if installed"
          apt:
            name:
                - "nginx"
            state: absent
        - name: stop Mysql
          service:
            name: mysql
            state: stopped
          when: "'mysql' in ansible_facts.services"
        - name: stop apache2
          service:
            name: apache2
            state: stopped
          when: "'apache2' in ansible_facts.services"

- name: Installing wordpress through source
  hosts: all
  become: True
  gather_facts: False
  vars:
    wprootdir: "/srv/www/wordpress"
  tasks:
    - name: checking if wp src dir exists
      stat:
        path: "{{ wprootdir }}"
      register: dir_details
    - name: delete existing wordpress source files
      become_user: www-data
      no_log: True
      file:
        path: "{{ wprootdir }}"
        state: absent
    - name: creating /var/www for wordpress source
      file:
        path: "/srv/www/wordpress" 
        recurse: yes
        state: directory
        owner: www-data
        mode: '0755'  
    - name: downloading and extracting wordpress source
      shell:
        cmd: "curl https://wordpress.org/latest.tar.gz | sudo -u www-data tar zx -C /srv/www"
      register: status
    - fail:
        msg: "Unable to download or extract wordpress source"
      when: (status.rc != 0)

- name: Configuring apache for wordpress
  hosts: all
  become: True
  gather_facts: False
  vars:
    wprootdir: "/srv/www/wordpress"
    wpconffile: "/etc/apache2/sites-available/wordpress.conf"
  tasks:
    - name: deleting the file if it exists
      file:
        path: "{{ wpconffile }}"
        state: absent
    - name: creating wordpress conf file
      file:
        path: "{{ wpconffile }}"
        state: touch
        owner: www-data
    - name: populating wordpress conf file
      template:
        src: apache2.j2
        dest: "{{ wpconffile }}"
    - name: enabling the site
      shell:
        cmd: "a2ensite wordpress"
    - name: enable URL rewriting
      shell:
        cmd: "a2enmod rewrite"
    - name: disable default "it works" site
      shell:
        cmd: "a2dissite 000-default"
    - name: restart apache2
      service:
        name: apache2
        state: reloaded

- name: Configuring database
  hosts: all
  become: True
  gather_facts: True
  vars:
    mysql_port: 3306
    mysql_socket: /var/run/mysqld/mysqld.sock
    mysql_superuser: root
    mysql_superuser_home: "{% if mysql_superuser == 'root' %}/root{% else %}/home/{{ mysql_superuser }}{% endif %}"
    mysql_superuser_password: SuperUserPwd
    mysql_wordpress_password: WordpressPwd
    http_port: 80  
  tasks:
    - name: Installing PyMySql through pip
      pip:
        name: PyMySql
        state: present
    - name: ensure mysql is running and starts on boot
      service:
        name: mysql
        state: started
        enabled: True
          
    - name: Removes anonymous user account for localhost
      community.mysql.mysql_user:
        name: ''
        state: absent
        login_user: root
        login_password: ""
        login_unix_socket: "{{ mysql_socket }}"
      when: ansible_local.mysqlinfo is undefined       

    - name: adding a password for root user
      mysql_user:
        name: "{{ mysql_superuser }}"
        host: localhost
        password: "{{ mysql_superuser_password }}"
        priv: "*.*:ALL,GRANT"
        login_user: root
        login_password: ""
        login_port: "{{ mysql_port }}"
        login_host: localhost
        login_unix_socket: "{{ mysql_socket }}"
        check_implicit_admin: yes
      when: ansible_local.mysqlinfo is undefined      
    - name: "Create custom fact directory"
      file:
        path: "/etc/ansible/facts.d"
        state: "directory"
        recurse: yes
      when: ansible_local.mysqlinfo is undefined      
    - name: "record mysql info in custom fact"
      template:
        src: mysqlinfo.j2
        dest: /etc/ansible/facts.d/mysqlinfo.fact
        mode: 0644
      when: ansible_local.mysqlinfo is undefined      
    - name: "re-run setup to use custom facts"
      setup:
        filter: ansible_local
      when: ansible_local.mysqlinfo is undefined      
    - debug:
        msg:
          - "mysqlinfo is {{ ansible_local.mysqlinfo }}"
      when: ansible_local.mysqlinfo is defined
        
    - name: create database wordpress
      mysql_db: 
        db: wordpress
        state: present
        login_user: "{{ ansible_local.mysqlinfo.mysql_superuser }}"
        login_password: "{{ ansible_local.mysqlinfo.mysql_superuser_password }}"
        login_unix_socket: "{{ mysql_socket }}"
      when: ansible_local.mysqlinfo is defined

    - name: Create database user 'wordpress' with all database privileges
      community.mysql.mysql_user:
        name: wordpress
        password: "{{ mysql_wordpress_password }}"
        login_user: "{{ ansible_local.mysqlinfo.mysql_superuser }}"
        login_password: "{{ ansible_local.mysqlinfo.mysql_superuser_password }}"
        priv: '*.*:ALL'
        state: present
      when: ansible_local.mysqlinfo is defined

    - name: Flush privileges
      mysql_query:
        login_db: wordpress
        login_user: "{{ ansible_local.mysqlinfo.mysql_superuser }}"
        login_password: "{{ ansible_local.mysqlinfo.mysql_superuser_password }}"
        login_unix_socket: "{{ mysql_socket }}"
        query: FLUSH PRIVILEGES 

     # UFW Configuration
    - name: "UFW - Allow HTTP on port {{ http_port }}"
      ufw:
        rule: allow
        port: "{{ http_port }}"
        proto: tcp
      notify:
      - Restart Mysql
      tags: [ system ]

  handlers:
    - name: Restart Mysql
      service:
        name: mysql
        state: restarted
    - name: Restart Apache2
      service:
        name: apache2
        state: restarted

- name: Configuring wordpress to connect to the database
  hosts: all
  gather_facts: False
  become: true
  vars:
    wpconfigfile: "/srv/www/wordpress/wp-config.php"
  tasks:
    - name: copy sample config to wp-config.php
      copy:
        remote_src: yes
        src: /srv/www/wordpress/wp-config-sample.php
        dest: "{{ wpconfigfile }}"
        owner: www-data

    - name: "re-run setup to use custom facts"
      setup:
        filter: ansible_local
    - name: set database credentials in the config file
      become: false
      command: "{{ item }}"
      with_items:
        - "sudo -u www-data sed -i s/database_name_here/wordpress/ {{ wpconfigfile }}"
        - "sudo -u www-data sed -i s/username_here/wordpress/ {{ wpconfigfile }}"
        - "sudo -u www-data sed -i s/password_here/{{ ansible_local.mysqlinfo.mysql_wordpress_password }}/ {{ wpconfigfile }}"
    - name: get random secret keys
      uri:
        url: https://api.wordpress.org/secret-key/1.1/salt/
        return_content: yes
        body_format: json
      register: wordpress_keys
    - debug:
        var: wordpress_keys.content
    - name: delete existing bak file
      file:
        path: "{{ wpconfigfile }}.bak"
        state: absent
    - name: run script to remove key placeholders
      become_user: www-data
      script:
        chdir: /srv/www/wordpress/
        cmd: replacelines.py
        executable: /usr/bin/python3
        environment: /srv/www/wordpress/
    - name: update config file
      become_user: www-data
      copy:
        remote_src: yes
        src: "{{ wpconfigfile }}.bak"
        dest: "{{ wpconfigfile }}"
    - blockinfile:
        path: "{{ wpconfigfile }}"
        marker: // {mark} ANSIBLE MANAGED BLOCK  
        block:
          "{{ wordpress_keys.content }}"

  handlers:
    - name: Restart Mysql
      service:
        name: mysql
        state: restarted
    - name: Restart Apache2
      service:
        name: apache2
        state: restarted

Jinja2模板文件

apache2.j2

<VirtualHost *:80>
    Servername {{ ansible_hostname }}
    DocumentRoot "{{ wprootdir }}"
    <Directory "{{ wprootdir }}">
        Options FollowSymLinks
        AllowOverride Limit Options FileInfo
        DirectoryIndex index.php
        Require all granted
    </Directory>
    <Directory "{{ wprootdir }}/wp-content">
        Options FollowSymLinks
        Require all granted
    </Directory>
    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

mysqlinfo.j2

{
    "mysql_port": "{{ mysql_port }}",
    "mysql_socket": "{{ mysql_socket }}",
    "mysql_superuser": "{{ mysql_superuser }}",
    "mysql_superuser_password": "{{ mysql_superuser_password }}",
    "mysql_wordpress_password": "{{ mysql_wordpress_password }}"
}

replacelines.py脚本

import re

with open("wp-config.php", "r") as wpconfig, open("wp-config.php.bak", "w") as wpconfigbak:
    for line in wpconfig:
        found = re.search(r'AUTH_KEY|SECURE_AUTH_KEY|LOGGED_IN_KEY|NONCE_KEY|AUTH_SALT|SECURE_AUTH_SALT|LOGGED_IN_SALT|NONCE_SALT', line.strip());
        if (not found):
            wpconfigbak.write(line)
        else:
            continue

inventory文件

[local]
localhost ansible_connection=local

执行命令

ansible-playbook -i inventory SetupWordpress.yaml

或通过仓库执行:

git clone -b WIP git@github.com:redbilledpanda/DevOpsScripts.git
cd DevOpsScripts && ansible-playbook -i inventory SetupWordpress.yaml

排查方向

结合"跳过盐值生成即可正常登录"的现象,问题大概率出在盐值替换流程中:

  1. blockinfile标记格式错误:当前使用// {mark} ANSIBLE MANAGED BLOCK作为标记,但wp-config.php是PHP文件,应使用PHP注释风格的标记(如/* {mark} ANSIBLE MANAGED BLOCK */),否则标记会被当作无效PHP代码,导致配置解析失败。
  2. replacelines.py脚本逻辑问题:脚本删除了所有包含盐值关键字的行,可能破坏了wp-config.php的代码结构(比如遗漏了盐值块的上下文注释或括号)。
  3. 盐值插入位置错误:blockinfile默认将内容插入到文件末尾,但WordPress要求盐值定义在特定位置(数据库配置之后、require_once(ABSPATH . 'wp-settings.php');之前),位置错误会导致盐值不生效。

内容的提问来源于stack exchange,提问作者Skegg

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 19:05:23